Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quarto-dev
GitHub Repository: quarto-dev/quarto-cli
Path: blob/main/src/resources/extensions/quarto/confluence/overrides.lua
12923 views
1
local function startsWith(text, prefix)
2
return text:find(prefix, 1, true) == 1
3
end
4
5
local function startsWithHttp(source)
6
return startsWith(source, "http")
7
end
8
9
local function escape(s, in_attribute)
10
return s:gsub("[<>&\"']",
11
function(x)
12
if x == '<' then
13
return '&lt;'
14
elseif x == '>' then
15
return '&gt;'
16
elseif x == '&' then
17
return '&amp;'
18
elseif in_attribute and x == '"' then
19
return '&quot;'
20
elseif in_attribute and x == "'" then
21
return '&#39;'
22
else
23
return x
24
end
25
end)
26
end
27
28
local function dumpObject(o)
29
if type(o) == 'table' then
30
local s = '{ '
31
for k,v in pairs(o) do
32
if type(k) ~= 'number' then k = '"'..k..'"' end
33
s = s .. '['..k..'] = ' .. dumpObject(v) .. ','
34
end
35
return s .. '} '
36
else
37
return tostring(o)
38
end
39
end
40
41
local function log(label, object)
42
print(label or '' .. ': ', dumpObject(object))
43
end
44
45
local function isEmpty(s)
46
return s == nil or s == ''
47
end
48
49
local function interpolate(str, vars)
50
-- Allow replace_vars{str, vars} syntax as well as replace_vars(str, {vars})
51
if not vars then
52
vars = str
53
str = vars[1]
54
end
55
return (string.gsub(str, "({([^}]+)})",
56
function(whole, i)
57
return vars[i] or whole
58
end))
59
end
60
61
function updateSizing(existingSizing, attributes, key)
62
local value = ''
63
if (attributes and attributes[key]) then
64
value = escape(attributes[key], '')
65
if not isEmpty(value) then
66
return existingSizing .. '\n ac:' .. key .. '="' .. value ..'"'
67
end
68
end
69
return existingSizing
70
end
71
72
function CaptionedImageConfluence(source, title, caption, attr, id)
73
--Note Title isn't usable by confluence at this time it will
74
-- serve as the default value for attr.alt-text
75
76
local CAPTION_SNIPPET = [[<ac:caption>{caption}</ac:caption>]]
77
78
local IMAGE_SNIPPET = [[{anchor}<ac:image
79
ac:align="{align}"
80
ac:layout="{layout}"{sizing}
81
ac:alt="{alt}">
82
<ri:attachment ri:filename="{source}" />{caption}
83
</ac:image>]]
84
85
local sourceValue = source
86
local titleValue = title
87
local captionValue = caption
88
local anchorValue = ""
89
local sizing = ""
90
91
if (id and #id > 0) then
92
-- wrap in `p` to prevent an issue with CSF not redering correctly
93
anchorValue = "<p>" .. HTMLAnchorConfluence(id) .. "</p>"
94
end
95
96
local alignValue = 'center'
97
if (attr and attr['fig-align']) then
98
alignValue = escape(attr['fig-align'], 'center')
99
end
100
101
local altValue = titleValue;
102
if (attr and attr['fig-alt']) then
103
altValue = escape(attr['fig-alt'], '')
104
end
105
106
local layoutValue = 'center'
107
if alignValue == 'right' then layoutValue = 'align-end' end
108
if alignValue == 'left' then layoutValue = 'align-start' end
109
110
sizing = updateSizing(sizing, attr, 'width')
111
sizing = updateSizing(sizing, attr, 'height')
112
113
if not isEmpty(captionValue) then
114
captionValue =
115
interpolate {
116
CAPTION_SNIPPET,
117
caption = escape(captionValue)}
118
end
119
120
if(not startsWithHttp(source)) then
121
return interpolate {
122
IMAGE_SNIPPET,
123
source = sourceValue,
124
align = alignValue,
125
layout = layoutValue,
126
sizing = sizing,
127
alt = altValue,
128
caption = captionValue,
129
anchor = anchorValue
130
}
131
end
132
133
return "<img src='" .. escape(source,true) .. "' title='" ..
134
escape(title,true) .. "'/>"
135
136
end
137
138
function LinkConfluence(source, target, title, attr)
139
-- For some reason, rendering converts spaces to a double line-break
140
source = string.gsub(source, "\n\n", " ")
141
source = string.gsub(source, "\n \n", " ")
142
source = string.gsub(source, "(\n", "")
143
source = string.gsub(source, "\n)", "")
144
145
local 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>]]
146
147
if(not startsWithHttp(target) and (not string.find(target, ".qmd"))) then
148
return interpolate {
149
LINK_ATTACHMENT_SNIPPET,
150
source = escape(source),
151
target = target,
152
doubleBraket = "]]"
153
}
154
end
155
156
return "<a href='" .. escape(target,true) .. "' title='" ..
157
escape(title,true) .. "'>" .. source .. "</a>"
158
end
159
160
function CalloutConfluence(type, content)
161
local 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>]]
162
163
local MAP_TYPE = {
164
note = {name = "info", id = "1c8062cd-87de-4701-a698-fd435e057468"},
165
warning = {name = "note", id = "1049a0d8-470f-4f83-a0d7-b6ad35ea8eda"},
166
important = {name = "warning", id = "0185f821-7aa4-404a-8748-ec59a46357e1"},
167
tip = {name = "tip", id = "97c39328-9651-4c56-8a8c-ab5537001d86"},
168
caution = {name = "note", id = "1049a0d8-470f-4f83-a0d7-b6ad35ea8eda"}
169
}
170
171
local mappedType = MAP_TYPE[type] or MAP_TYPE['note']
172
173
return interpolate {
174
SNIPPET,
175
type = mappedType.name,
176
id = mappedType.id,
177
content = content}
178
end
179
180
function CodeBlockConfluence(codeValue, languageValue)
181
local CODE_SNIPPET = [[<ac:structured-macro
182
ac:name="code"
183
ac:schema-version="1"
184
ac:macro-id="1d1a2d13-0179-4d8f-b448-b28dfaceea4a">
185
<ac:parameter ac:name="language">{languageValue}</ac:parameter>
186
<ac:plain-text-body>
187
<![CDATA[{codeValue}{doubleBraket}>
188
</ac:plain-text-body>
189
</ac:structured-macro>]]
190
191
return interpolate {
192
CODE_SNIPPET,
193
languageValue = languageValue or '',
194
codeValue = codeValue,
195
doubleBraket = ']]'
196
}
197
end
198
199
function HTMLAnchorConfluence(id)
200
if (not id or #id == 0) then
201
return ""
202
end
203
204
local 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>]]
205
206
return interpolate {
207
SNIPPET,
208
id = id or ''
209
}
210
end
211
212
function RawInlineConfluence(text)
213
if type(text) ~= "string" then return text end
214
215
-- Confluence expects closed Void Elements as proper XHTML
216
-- https://github.com/quarto-dev/quarto-cli/issues/4479
217
218
if (string.lower(text) == [[<br>]]) then
219
return "<br/>"
220
end
221
222
local match = string.match(text, "<img (.-)>")
223
local matchClosed = string.match(text, "<img (.-)/>")
224
if (match and not matchClosed) then
225
return string.gsub(text, ">", "/>")
226
end
227
228
return text
229
end
230
231
return {
232
CaptionedImageConfluence = CaptionedImageConfluence,
233
CodeBlockConfluence = CodeBlockConfluence,
234
LinkConfluence = LinkConfluence,
235
TableConfluence = TableConfluence,
236
CalloutConfluence = CalloutConfluence,
237
HTMLAnchorConfluence = HTMLAnchorConfluence,
238
RawInlineConfluence = RawInlineConfluence,
239
escape = escape,
240
interpolate = interpolate
241
}
242
243