class HTML < Generic
- Inherits from
Generic: #out, #render, #reference_def, #cr, #block, #container, #plain
Renders Markdown node trees as HTML.
Definitions
def initialize(ids: false, headings: nil, tight: false, **options)
Initializes an HTML renderer.
Signature
-
parameter
idsBoolean Whether to wrap headings in anchored sections.
-
parameter
headingsMarkly::Renderer::Headings | Nil A heading tracker to reuse.
-
parameter
tightBoolean Whether to render paragraphs tightly.
-
option
:flagsInteger The enabled rendering flags.
-
option
:extensionsArray(Symbol) The enabled extensions.
Implementation
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
def document(_)
Renders a complete document and closes any generated sections.
Signature
-
parameter
_Markly::Node The document node.
Implementation
def document(_)
@section = false
super
out("</ol>\n</section>\n") if @written_footnote_ix
out("</section>") if @section
end
def id_for(node)
Returns an escaped HTML id attribute for a heading node.
Signature
-
parameter
nodeMarkly::Node The heading node.
-
returns
String | Nil The id attribute when heading IDs are enabled.
Implementation
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
def self.anchor_for(node)
Generates a normalized anchor from a node's plain-text content.
Signature
-
parameter
nodeMarkly::Node The node to convert.
-
returns
String The normalized anchor.
Implementation
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
def anchor_for(node)
Generates a normalized anchor from a node's plain-text content.
Signature
-
parameter
nodeMarkly::Node The node to convert.
-
returns
String The normalized anchor.
Implementation
def anchor_for(node)
self.class.anchor_for(node)
end
def header(node)
Renders a heading node, optionally wrapped in an anchored section.
Signature
-
parameter
nodeMarkly::Node The heading node.
Implementation
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
def paragraph(node)
Renders a paragraph node.
Signature
-
parameter
nodeMarkly::Node The paragraph node.
Implementation
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
def list(node)
Renders an ordered or unordered list node.
Signature
-
parameter
nodeMarkly::Node The list node.
Implementation
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
def list_item(node)
Renders a list-item node, including task-list attributes when present.
Signature
-
parameter
nodeMarkly::Node The list-item node.
Implementation
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
def tasklist(node)
Returns the HTML fragment required for a task-list item.
Signature
-
parameter
nodeMarkly::Node The list-item node.
-
returns
String The task-list fragment, or an empty string.
Implementation
def tasklist(node)
return "" unless tasklist?(node)
state = if checked?(node)
'checked="" disabled=""'
else
'disabled=""'
end
"><input type=\"checkbox\" #{state} /"
end
def blockquote(node)
Renders a blockquote node.
Signature
-
parameter
nodeMarkly::Node The blockquote node.
Implementation
def blockquote(node)
block do
container("<blockquote#{source_position(node)}>\n", "</blockquote>") do
out(:children)
end
end
end
def hrule(node)
Renders a thematic-break node.
Signature
-
parameter
nodeMarkly::Node The thematic-break node.
Implementation
def hrule(node)
block do
out("<hr#{source_position(node)} />")
end
end
def code_block(node)
Renders a code block and its optional language metadata.
Signature
-
parameter
nodeMarkly::Node The code block node.
Implementation
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
def html(node)
Renders or omits a raw block-level HTML node according to the flags.
Signature
-
parameter
nodeMarkly::Node The raw HTML node.
Implementation
def html(node)
block do
if flag_enabled?(UNSAFE)
out(tagfilter(node.string_content))
else
out("<!-- raw HTML omitted -->")
end
end
end
def inline_html(node)
Renders or omits a raw inline HTML node according to the flags.
Signature
-
parameter
nodeMarkly::Node The raw inline HTML node.
Implementation
def inline_html(node)
if flag_enabled?(UNSAFE)
out(tagfilter(node.string_content))
else
out("<!-- raw HTML omitted -->")
end
end
def emph(node)
Renders an emphasized inline node.
Signature
-
parameter
nodeMarkly::Node The emphasized node.
Implementation
def emph(node)
out("<em>", :children, "</em>")
end
def strong(node)
Renders a strongly emphasized inline node.
Signature
-
parameter
nodeMarkly::Node The strong node.
Implementation
def strong(node)
if node.parent.nil? || node.parent.type == node.type
out(:children)
else
out("<strong>", :children, "</strong>")
end
end
def link(node)
Renders a link node with escaped destination and title attributes.
Signature
-
parameter
nodeMarkly::Node The link node.
Implementation
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
def image(node)
Renders an image node with plain-text alternative content.
Signature
-
parameter
nodeMarkly::Node The image node.
Implementation
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
def text(node)
Renders an escaped text node.
Signature
-
parameter
nodeMarkly::Node The text node.
Implementation
def text(node)
out(escape_html(node.string_content))
end
def code(node)
Renders an inline code node and its optional language metadata.
Signature
-
parameter
nodeMarkly::Node The inline code node.
Implementation
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
def linebreak(_node)
Renders a hard line break.
Signature
-
parameter
_nodeMarkly::Node The line-break node.
Implementation
def linebreak(_node)
out("<br />\n")
end
def softbreak(_)
Renders a soft line break according to the configured flags.
Signature
-
parameter
_Markly::Node The soft-break node.
Implementation
def softbreak(_)
if flag_enabled?(HARD_BREAKS)
out("<br />\n")
elsif flag_enabled?(NO_BREAKS)
out(" ")
else
out("\n")
end
end
def table(node)
Renders a table node and initializes its column alignments.
Signature
-
parameter
nodeMarkly::Node The table node.
Implementation
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
def table_header(node)
Renders a table-header row.
Signature
-
parameter
nodeMarkly::Node The table-header node.
Implementation
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
def table_row(node)
Renders a table row, opening the table body when necessary.
Signature
-
parameter
nodeMarkly::Node The table-row node.
Implementation
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
TABLE_CELL_ALIGNMENT
Signature
- constant
HTML attributes for table-cell alignments.
Implementation
TABLE_CELL_ALIGNMENT = {
left: ' align="left"',
right: ' align="right"',
center: ' align="center"'
}.freeze
def table_cell(node)
Renders a table cell using the current column alignment.
Signature
-
parameter
nodeMarkly::Node The table-cell node.
Implementation
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
def strikethrough(_)
Renders a strikethrough node.
Signature
-
parameter
_Markly::Node The strikethrough node.
Implementation
def strikethrough(_)
out("<del>", :children, "</del>")
end
def footnote_reference(node)
Renders a footnote reference linking to its definition.
Signature
-
parameter
nodeMarkly::Node The footnote-reference node.
Implementation
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
def footnote_definition(node)
Renders a footnote definition and records its backlink target.
Signature
-
parameter
nodeMarkly::Node The footnote-definition node.
Implementation
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