class Document

Represents a Markdown document with optional source code cross-references.

Definitions

def initialize(text, base = nil, definition: nil, default_language: nil)

Initialize a document from Markdown text.

Signature

parameter text String

The Markdown source text.

parameter base Base | Nil

The project used to resolve source code references.

parameter definition Decode::Definition | Nil

The definition that provides the lexical context.

parameter default_language Decode::Language::Generic | Nil

The default language for source code references.

Implementation

def initialize(text, base = nil, definition: nil, default_language: nil)
	@text = text
	@base = base
	@index = base&.index
	
	@definition = definition
	@default_language = default_language
	
	@root = nil
end

def root

Parse and resolve the document root.

Signature

returns Markly::Node

The root document node.

Implementation

def root
	@root ||= resolve(Markly.parse(@text, flags: Markly::INLINE_CODE_INFO, extensions: [:table]))
end

def title

Extract the leading heading as the document title.

Signature

returns String | Nil

The title, if the document starts with a heading.

Implementation

def title
	child = self.root.first_child
	
	if child && child.type == :header
		return child.first_child.to_plaintext
	end
end

def first_child

Get the first node in the document.

Signature

returns Markly::Node | Nil

The first child node.

Implementation

def first_child
	self.root.first_child
end

def replace_section(name, children: false)

Remove a named section and yield its heading for replacement.

Signature

parameter name String

A fragment of the heading text to match.

parameter children Boolean

Whether to remove nested subsections too.

yields {|header| ...}

The matched heading node.

parameter header Markly::Node

The matched heading.

Implementation

def replace_section(name, children: false)
	child = self.first_child
	
	while child
		if child.type == :header
			header = child
			
			# We found the matched header:
			if header.first_child.to_plaintext.include?(name)
				# Now subsequent children:
				current = header.next
				
				# Delete everything in the section until we encounter another header:
				while current
					if current.type == :header
						# If we are removing all children, keep on going until we reach a header of the same level or higher:
						if children
							break if current.header_level <= header.header_level
						else
							break
						end
					end
					
					current_next = current.next
					current.delete
					current = current_next
				end
				
				return yield(header)
			end
		end
		
		child = child.next
	end
end

def to_markdown(**options)

Render the document as Markdown.

Signature

returns String

The rendered Markdown.

Implementation

def to_markdown(**options)
	self.root.to_markdown(**options)
end

def to_html(node = self.root, **options)

Render a document node as HTML.

Signature

parameter node Markly::Node

The node to render.

returns XRB::MarkupString

The rendered HTML markup.

Implementation

def to_html(node = self.root, **options)
	renderer = Renderer.new(
		inline_code_resolver: (@index ? method(:reference_node) : nil),
		ids: true,
		flags: Markly::UNSAFE,
		**options
	)
	XRB::Markup.raw(renderer.render(node))
end

def paragraph_node(child)

Wrap a node in a paragraph.

Signature

parameter child Markly::Node

The node to wrap.

returns Markly::Node

The paragraph node.

Implementation

def paragraph_node(child)
	node = Markly::Node.new(:paragraph)
	node.append_child(child)
	return node
end

def html_node(content, type = :html)

Build an HTML block node.

Signature

parameter content String

The raw HTML content.

parameter type Symbol

The node type retained for compatibility.

returns Markly::Node

The HTML node.

Implementation

def html_node(content, type = :html)
	node = Markly::Node.new(:html)
	node.string_content = content
	return node
end

def inline_html_node(content)

Build an inline HTML node.

Signature

parameter content String

The raw HTML content.

returns Markly::Node

The inline HTML node.

Implementation

def inline_html_node(content)
	node = Markly::Node.new(:inline_html)
	node.string_content = content
	return node
end

def text_node(content)

Build a text node.

Signature

parameter content String

The text content.

returns Markly::Node

The text node.

Implementation

def text_node(content)
	node = Markly::Node.new(:text)
	node.string_content = content
	return node
end

def code_node(content, language = nil)

Build an inline code node.

Signature

parameter content String

The code content.

parameter language String | Nil

The source language name.

returns Markly::Node

The code node.

Implementation

def code_node(content, language = nil)
	node = Markly::Node.new(:code)
	node.string_content = content
	node.code_info = language if language
	
	return node
end

def reference_node(content, language: nil)

Resolve a source code reference to HTML.

Signature

parameter content String

The source code reference.

parameter language String | Nil

The explicit source language.

returns Markly::Node

The inline HTML node.

Implementation

def reference_node(content, language: nil)
	reference, definition = resolve_reference(content, language: language)
	
	if definition
		content = definition.qualified_form
		language = reference.language.name
	elsif reference
		content = reference.identifier
		language = reference.language.name
	end
	
	attributes = {}
	attributes[:class] = "language-#{language}" if language
	
	markup = XRB::Builder.fragment do |builder|
		builder.inline("code", attributes) do
			if definition
				builder.inline("a", href: @base.link_for(definition), title: reference.identifier) do
					builder.text(content)
				end
			else
				builder.text(content)
			end
		end
	end
	
	return inline_html_node(markup.to_s)
end

def resolve_reference(content, language: nil)

Resolve source code reference metadata and its indexed definition.

Signature

parameter content String

The source code reference.

parameter language String | Nil

The explicit source language.

returns Array

The parsed reference and resolved definition.

Implementation

def resolve_reference(content, language: nil)
	reference = if language
		@index.languages.reference_for(language, content)
	else
		@index.languages.parse_reference(content, default_language: @default_language)
	end
	
	definition = @index.lookup(reference, relative_to: @definition) if reference
	
	return reference, definition
end