Class: Markly::Renderer::HTML

Inherits:
Generic
  • Object
show all
Defined in:
lib/markly/renderer/html.rb

Overview

Renders Markdown node trees as HTML.

Constant Summary collapse

TABLE_CELL_ALIGNMENT =
{
	left: ' align="left"',
	right: ' align="right"',
	center: ' align="center"'
}.freeze

Instance Attribute Summary

Attributes inherited from Generic

#in_plain, #in_tight

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Generic

#Whether the renderer is emitting plain text.=, #Whether the renderer is inside a tight container.=, #block, #container, #cr, #out, #plain, #reference_def, #render

Constructor Details

#initialize(ids: false, headings: nil, tight: false, **options) ⇒ HTML

Initializes an HTML renderer.

Parameters:

  • :flags (Hash)

    a customizable set of options

  • :extensions (Hash)

    a customizable set of options



27
28
29
30
31
32
33
34
35
36
37
# File 'lib/markly/renderer/html.rb', line 27

def initialize(ids: false, headings: nil, tight: false, **options)
	super(**options)
	
	# Initialize heading tracker if IDs are enabled
	@headings = headings || (ids ? Headings.new : nil)
	
	@section = nil
	@tight = tight
	
	@footnotes = {}
end

Class Method Details

.anchor_for(node) ⇒ Object

Generates a normalized anchor from a node's plain-text content.



65
66
67
68
69
70
71
72
73
# File 'lib/markly/renderer/html.rb', line 65

def self.anchor_for(node)
	# Convert to plaintext, strip trailing whitespace, convert to lowercase:
	text = node.to_plaintext.chomp.downcase
	
	# Replace sequences of whitespace with hyphens:
	text.gsub!(/\s+/, "-")
	
	return text
end

Instance Method Details

#anchor_for(node) ⇒ Object

Generates a normalized anchor from a node's plain-text content.



79
80
81
# File 'lib/markly/renderer/html.rb', line 79

def anchor_for(node)
	self.class.anchor_for(node)
end

#blockquote(node) ⇒ Object

Renders a blockquote node.



174
175
176
177
178
179
180
# File 'lib/markly/renderer/html.rb', line 174

def blockquote(node)
	block do
		container("<blockquote#{source_position(node)}>\n", "</blockquote>") do
			out(:children)
		end
	end
end

#code(node) ⇒ Object

Renders an inline code node and its optional language metadata.



288
289
290
291
292
293
294
295
# File 'lib/markly/renderer/html.rb', line 288

def code(node)
	language = node.code_language
	out("<code")
	out(' class="language-', language, '"') if language
	out(">")
	out(escape_html(node.string_content))
	out("</code>")
end

#code_block(node) ⇒ Object

Renders a code block and its optional language metadata.



194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
# File 'lib/markly/renderer/html.rb', line 194

def code_block(node)
	block do
		language = node.code_language
		
		if flag_enabled?(GITHUB_PRE_LANG)
			out("<pre#{source_position(node)}")
			out(' lang="', language, '"') if language
			out("><code>")
		else
			out("<pre#{source_position(node)}><code")
			if language
				out(' class="language-', language, '">')
			else
				out(">")
			end
		end
		out(escape_html(node.string_content))
		out("</code></pre>")
	end
end

#document(_) ⇒ Object

Renders a complete document and closes any generated sections.



42
43
44
45
46
47
# File 'lib/markly/renderer/html.rb', line 42

def document(_)
	@section = false
	super
	out("</ol>\n</section>\n") if @written_footnote_ix
	out("</section>") if @section
end

#emph(node) ⇒ Object

Renders an emphasized inline node.



242
243
244
# File 'lib/markly/renderer/html.rb', line 242

def emph(node)
	out("<em>", :children, "</em>")
end

#footnote_definition(node) ⇒ Object

Renders a footnote definition and records its backlink target.



387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/markly/renderer/html.rb', line 387

def footnote_definition(node)
	unless @footnote_ix
		out("<section class=\"footnotes\" data-footnotes>\n<ol>\n")
		@footnote_ix = 0
	end
	
	@footnote_ix += 1
	label = node.string_content
	@footnotes[@footnote_ix] = label
	
	out("<li id=\"fn-#{label}\">\n", :children)
	out("\n") if out_footnote_backref
	out("</li>\n")
	# </ol>
	# </section>
end

#footnote_reference(node) ⇒ Object

Renders a footnote reference linking to its definition.



377
378
379
380
381
382
# File 'lib/markly/renderer/html.rb', line 377

def footnote_reference(node)
	label = node.parent_footnote_def.string_content
	
	out("<sup class=\"footnote-ref\"><a href=\"#fn-#{label}\" id=\"fnref-#{label}\" data-footnote-ref>#{node.string_content}</a></sup>")
	# out(node.to_html)
end

#header(node) ⇒ Object

Renders a heading node, optionally wrapped in an anchored section.



86
87
88
89
90
91
92
93
94
95
96
# File 'lib/markly/renderer/html.rb', line 86

def header(node)
	block do
		if @headings
			out("</section>") if @section
			@section = true
			out("<section#{id_for(node)}>")
		end
		
		out("<h", node.header_level, "#{source_position(node)}>", :children, "</h", node.header_level, ">")
	end
end

#hrule(node) ⇒ Object

Renders a thematic-break node.



185
186
187
188
189
# File 'lib/markly/renderer/html.rb', line 185

def hrule(node)
	block do
		out("<hr#{source_position(node)} />")
	end
end

#html(node) ⇒ Object

Renders or omits a raw block-level HTML node according to the flags.



218
219
220
221
222
223
224
225
226
# File 'lib/markly/renderer/html.rb', line 218

def html(node)
	block do
		if flag_enabled?(UNSAFE)
			out(tagfilter(node.string_content))
		else
			out("<!-- raw HTML omitted -->")
		end
	end
end

#id_for(node) ⇒ Object

Returns an escaped HTML id attribute for a heading node.



53
54
55
56
57
58
59
# File 'lib/markly/renderer/html.rb', line 53

def id_for(node)
	if @headings
		anchor = @headings.anchor_for(node)
		# `CGI.escape_html` is not exposed by `cgi/escape` on Ruby 3.4:
		return " id=\"#{CGI.escapeHTML anchor}\""
	end
end

#image(node) ⇒ Object

Renders an image node with plain-text alternative content.



269
270
271
272
273
274
275
276
# File 'lib/markly/renderer/html.rb', line 269

def image(node)
	out('<img src="', escape_href(node.url), '"')
	plain do
		out(' alt="', :children, '"')
	end
	out(' title="', escape_html(node.title), '"') if node.title && !node.title.empty?
	out(" />")
end

#inline_html(node) ⇒ Object

Renders or omits a raw inline HTML node according to the flags.



231
232
233
234
235
236
237
# File 'lib/markly/renderer/html.rb', line 231

def inline_html(node)
	if flag_enabled?(UNSAFE)
		out(tagfilter(node.string_content))
	else
		out("<!-- raw HTML omitted -->")
	end
end

#linebreak(_node) ⇒ Object

Renders a hard line break.



300
301
302
# File 'lib/markly/renderer/html.rb', line 300

def linebreak(_node)
	out("<br />\n")
end

Renders a link node with escaped destination and title attributes.



260
261
262
263
264
# File 'lib/markly/renderer/html.rb', line 260

def link(node)
	out('<a href="', node.url.nil? ? "" : escape_href(node.url), '"')
	out(' title="', escape_html(node.title), '"') if node.title && !node.title.empty?
	out(">", :children, "</a>")
end

#list(node) ⇒ Object

Renders an ordered or unordered list node.



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/markly/renderer/html.rb', line 120

def list(node)
	old_tight = @tight
	@tight = node.list_tight
	
	block do
		if node.list_type == :bullet_list
			container("<ul#{source_position(node)}>\n", "</ul>") do
				out(:children)
			end
		else
			start = if node.list_start == 1
				"<ol#{source_position(node)}>\n"
			else
				"<ol start=\"#{node.list_start}\"#{source_position(node)}>\n"
			end
			container(start, "</ol>") do
				out(:children)
			end
		end
	end
	
	@tight = old_tight
end

#list_item(node) ⇒ Object

Renders a list-item node, including task-list attributes when present.



147
148
149
150
151
152
153
154
# File 'lib/markly/renderer/html.rb', line 147

def list_item(node)
	block do
		tasklist_data = tasklist(node)
		container("<li#{source_position(node)}#{tasklist_data}>#{' ' if tasklist?(node)}", "</li>") do
			out(:children)
		end
	end
end

#paragraph(node) ⇒ Object

Renders a paragraph node.



101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
# File 'lib/markly/renderer/html.rb', line 101

def paragraph(node)
	if @tight && node.parent.type != :blockquote
		out(:children)
	else
		block do
			container("<p#{source_position(node)}>", "</p>") do
				out(:children)
				if node.parent.type == :footnote_definition && node.next.nil?
					out(" ")
					out_footnote_backref
				end
			end
		end
	end
end

#softbreak(_) ⇒ Object

Renders a soft line break according to the configured flags.



307
308
309
310
311
312
313
314
315
# File 'lib/markly/renderer/html.rb', line 307

def softbreak(_)
	if flag_enabled?(HARD_BREAKS)
		out("<br />\n")
	elsif flag_enabled?(NO_BREAKS)
		out(" ")
	else
		out("\n")
	end
end

#strikethrough(_) ⇒ Object

Renders a strikethrough node.



370
371
372
# File 'lib/markly/renderer/html.rb', line 370

def strikethrough(_)
	out("<del>", :children, "</del>")
end

#strong(node) ⇒ Object

Renders a strongly emphasized inline node.



249
250
251
252
253
254
255
# File 'lib/markly/renderer/html.rb', line 249

def strong(node)
	if node.parent.nil? || node.parent.type == node.type
		out(:children)
	else
		out("<strong>", :children, "</strong>")
	end
end

#table(node) ⇒ Object

Renders a table node and initializes its column alignments.



320
321
322
323
324
325
326
# File 'lib/markly/renderer/html.rb', line 320

def table(node)
	@alignments = node.table_alignments
	@needs_close_tbody = false
	out("<table#{source_position(node)}>\n", :children)
	out("</tbody>\n") if @needs_close_tbody
	out("</table>\n")
end

#table_cell(node) ⇒ Object

Renders a table cell using the current column alignment.



361
362
363
364
365
# File 'lib/markly/renderer/html.rb', line 361

def table_cell(node)
	align = TABLE_CELL_ALIGNMENT.fetch(@alignments[@column_index], "")
	out(@in_header ? "<th#{align}#{source_position(node)}>" : "<td#{align}#{source_position(node)}>", :children, @in_header ? "</th>\n" : "</td>\n")
	@column_index += 1
end

#table_header(node) ⇒ Object

Renders a table-header row.



331
332
333
334
335
336
337
# File 'lib/markly/renderer/html.rb', line 331

def table_header(node)
	@column_index = 0
	
	@in_header = true
	out("<thead>\n<tr#{source_position(node)}>\n", :children, "</tr>\n</thead>\n")
	@in_header = false
end

#table_row(node) ⇒ Object

Renders a table row, opening the table body when necessary.



342
343
344
345
346
347
348
349
# File 'lib/markly/renderer/html.rb', line 342

def table_row(node)
	@column_index = 0
	if !@in_header && !@needs_close_tbody
		@needs_close_tbody = true
		out("<tbody>\n")
	end
	out("<tr#{source_position(node)}>\n", :children, "</tr>\n")
end

#tasklist(node) ⇒ Object

Returns the HTML fragment required for a task-list item.



160
161
162
163
164
165
166
167
168
169
# File 'lib/markly/renderer/html.rb', line 160

def tasklist(node)
	return "" unless tasklist?(node)
	
	state = if checked?(node)
		'checked="" disabled=""'
	else
		'disabled=""'
	end
	"><input type=\"checkbox\" #{state} /"
end

#text(node) ⇒ Object

Renders an escaped text node.



281
282
283
# File 'lib/markly/renderer/html.rb', line 281

def text(node)
	out(escape_html(node.string_content))
end