Jump to content

Module:etymon/tree

Wiktionary වෙතින්


local export = {}

local html_create = mw.html.create
local max = math.max

local function create_vertical_connector()
	return html_create('span'):addClass('etytree-connector-vertical')
end

local function create_abbr(text, title, glossary)
	local abbr = html_create('abbr')
		:attr('title', title)
		:wikitext(text)

	if glossary then
		abbr = '[[Appendix:Glossary#' .. glossary .. '|' .. tostring(abbr) .. ']]'
	end

	return html_create('span'):addClass('etytree-label'):node(abbr)
end

local function create_uncertainty_marker()
	return html_create('abbr')
		:addClass('etytree-unc')
		:attr('title', 'uncertain')
		:wikitext('?')
end

local function create_label_container()
	return html_create('span'):addClass('etytree-label-container')
end

local function render_label(term_block, keyword_info, keyword_modifiers, is_uncertain, is_group_child, term_labels)
	-- Skip label for invisible keywords
	local has_label = keyword_info and keyword_info.abbrev and not is_group_child and not keyword_info.invisible

	-- For group children, keyword uncertainty is shown on the group label, not on individual terms
	local keyword_uncertain = keyword_modifiers and keyword_modifiers.unc and not is_group_child
	local show_term_uncertainty = is_uncertain
	
	-- Check if we have term-specific labels
	local has_term_labels = term_labels and #term_labels > 0

	if not has_label and not show_term_uncertainty and not keyword_uncertain and not has_term_labels then
		return
	end

	local label_span = create_label_container()

	if has_label then
		local glossary_title = keyword_info.glossary
			and keyword_info.glossary:gsub("_", " ")
			or keyword_info.abbrev

		label_span:node(create_abbr(
			keyword_info.abbrev,
			glossary_title,
			keyword_info.glossary
		))

		-- Show uncertainty marker if term or keyword is uncertain
		if show_term_uncertainty or keyword_uncertain then
			label_span:node(create_uncertainty_marker())
		end
	else
		-- No label, but term or keyword is uncertain
		if show_term_uncertainty or keyword_uncertain then
			label_span:node(create_uncertainty_marker())
		end
	end

	-- Add term-specific labels
	if has_term_labels then
		for _, label_info in ipairs(term_labels) do
			label_span:node(create_abbr(label_info.abbrev, label_info.title, label_info.glossary))
		end
	end

	term_block:node(label_span)
end

local function render_term_block(node_data, format_term_func, is_toplevel)
	local link_content = html_create()
	link_content
		:tag('span')
		:addClass('etyl')
		:wikitext(node_data.lang:getCanonicalName())
		:done()

	local term_text = format_term_func(node_data, is_toplevel)

	if term_text then
		link_content
			:wikitext(' ')
			:tag('span')
			:addClass('etytree-term')
			:wikitext(term_text)
			:done()
	end

	local block = html_create('div'):addClass('etytree-block'):node(link_content)

	-- Add duplicate styling if this is a duplicate node
	if node_data.is_duplicate then
		block:addClass('etytree-duplicate')
	end

	return block
end

local function create_dotted_connector()
	return html_create('span'):addClass('etytree-connector-dotted')
end

-- Create an L-shaped connector for nodes with hidden ancestry (duplicate or no_child_categories)
local function create_duplicate_connector()
	local container = html_create('div'):addClass('etytree-duplicate-connector')

	local inner_wrapper = container:tag('div')
	inner_wrapper:tag('span'):addClass('etytree-dup-right')
	inner_wrapper:tag('span'):addClass('etytree-dup-horiz')
	inner_wrapper:tag('span'):addClass('etytree-dup-left')
	inner_wrapper:tag('span'):addClass('etytree-dup-arrow'):wikitext('▲')

	return container
end

local function render_group_label(connecting_line, keyword_info, keyword_modifiers)
	local has_abbrev = keyword_info and keyword_info.abbrev
	local keyword_uncertain = keyword_modifiers and keyword_modifiers.unc

	-- Nothing to show if no abbrev and no uncertainty
	if not has_abbrev and not keyword_uncertain then
		return
	end

	local label_span = connecting_line:tag('span'):addClass('etytree-group-label')

	if has_abbrev then
		local glossary_title = keyword_info.glossary
			and keyword_info.glossary:gsub("_", " ")
			or keyword_info.abbrev
		label_span:node(create_abbr(keyword_info.abbrev, glossary_title, nil))
	end

	-- Add uncertainty marker if keyword has <unc> modifier
	if keyword_uncertain then
		label_span:node(create_uncertainty_marker())
	end
end

local function add_branch_connector(column, index, total)
	if index == 1 then
		column:tag('span'):addClass('etytree-branch-left')
	elseif index == total then
		column:tag('span'):addClass('etytree-branch-right')
	else
		column:tag('span'):addClass('etytree-connector-vertical-short')
		column:tag('span'):addClass('etytree-branch-mid')
	end
end

function export.render(opts)
	opts = opts or {}
	local data_tree = opts.data_tree
	local format_term_func = opts.format_term_func

	-- Forward declaration
	local render_term

	-- Render a container (keyword + its terms)
	local function render_container(container, is_toplevel)
		local keyword_info = container.keyword_info
		local keyword_modifiers = container.keyword_modifiers or {}
		local is_group = keyword_info and keyword_info.is_group
		local terms = container.terms or {}

		-- Skip invisible keywords entirely
		if keyword_info and keyword_info.invisible then
			return nil, 0, 0
		end

		if #terms == 0 then
			return nil, 0, 0
		end

		-- For no_child_categories keywords (calque, semantic loan, etc.), don't render term's children
		local skip_child_rendering = keyword_info and keyword_info.no_child_categories

		-- Render each term in the container
		local rendered_terms = {}
		local container_height = 0
		local container_width = 0

		for _, term in ipairs(terms) do
			-- Collect term-specific labels
			local term_labels = {}
			if term.bor then
				table.insert(term_labels, { abbrev = "bor.", title = "borrowed", glossary = "loanword" })
			end
			
			local term_tree, term_height, term_width = render_term(term, keyword_info, keyword_modifiers, is_group, false, skip_child_rendering, term_labels)
			table.insert(rendered_terms, {
				tree = term_tree,
				height = term_height,
				width = term_width,
				is_uncertain = term.is_uncertain,
			})
			container_height = max(container_height, term_height)
			container_width = container_width + term_width
		end

		local rendered_html
		local has_connector = false

		if #rendered_terms == 1 then
			-- Single term: just return it directly
			rendered_html = rendered_terms[1].tree
			container_height = rendered_terms[1].height
			container_width = rendered_terms[1].width
		else
			-- Multiple terms: group them together
			local subtree_container = html_create('div'):addClass('etytree-branch-group')

			for i, term_data in ipairs(rendered_terms) do
				local column = html_create('div'):addClass('etytree-branch')
				column:node(term_data.tree)
				add_branch_connector(column, i, #rendered_terms)
				subtree_container:node(column)
			end

			local connecting_line = create_vertical_connector()

			-- Add group label for group keywords
			if is_group then
				render_group_label(connecting_line, keyword_info, keyword_modifiers)
			end

			rendered_html = html_create()
				:node(subtree_container)
				:node(connecting_line)
			has_connector = true
		end


		return rendered_html, container_height, container_width, has_connector
	end

	-- Render a term node
	render_term = function(term_node, keyword_info, keyword_modifiers, is_group_child, is_toplevel_term, skip_child_rendering, term_labels)
		local tree_width, tree_height = 0, 0
		local subtrees = {}

		-- Process term's children (which are containers)
		local has_hidden_children = false
		if not term_node.is_duplicate and not skip_child_rendering then
			for _, container in ipairs(term_node.children or {}) do
				local subtree, sub_height, sub_width, subtree_has_connector = render_container(container, is_toplevel_term)
				if subtree then
					table.insert(subtrees, {
						tree = subtree,
						height = sub_height,
						width = sub_width,
						has_connector = subtree_has_connector,
					})
					tree_height = max(tree_height, sub_height)
					tree_width = tree_width + sub_width
				end
			end
		elseif skip_child_rendering then
			-- Check if there are any visible children
			-- When stop_recursion is true, children aren't parsed, but has_visible_children flag is set
			if term_node.has_visible_children then
				has_hidden_children = true
			elseif term_node.children and #term_node.children > 0 then
				-- Fallback: check parsed children for visibility
				for _, container in ipairs(term_node.children) do
					local child_keyword_info = container.keyword_info
					if not (child_keyword_info and child_keyword_info.invisible) then
						has_hidden_children = true
						break
					end
				end
			end
		end

		local is_toplevel_node = (keyword_info == nil)
		local term_block = render_term_block(term_node, format_term_func, is_toplevel_node)
		render_label(term_block, keyword_info, keyword_modifiers, term_node.is_uncertain, is_group_child, term_labels or {})

		local term_html = html_create()

		if #subtrees == 0 then
			local show_connector = (term_node.is_duplicate and term_node.original_has_children) or has_hidden_children
			if show_connector then
				term_html:node(create_duplicate_connector())
			end
			term_html:node(term_block)
			tree_width = tree_width + 1
		elseif #subtrees == 1 then
			term_html:node(subtrees[1].tree)
			if not subtrees[1].has_connector then
				term_html:node(create_vertical_connector())
			end
			term_html:node(term_block)
		else
			-- Multiple containers: need to merge them
			local subtree_container = html_create('div'):addClass('etytree-branch-group')

			for i, subtree_data in ipairs(subtrees) do
				local column = html_create('div'):addClass('etytree-branch')
				column:node(subtree_data.tree)
				add_branch_connector(column, i, #subtrees)
				subtree_container:node(column)
			end

			local connecting_line = create_vertical_connector()

			term_html
				:node(subtree_container)
				:node(connecting_line)
				:node(term_block)
		end

		return term_html, tree_height + 1, tree_width
	end

	local final_tree, final_height, final_width = render_term(data_tree, nil, nil, false, true)

	local container = html_create('div')
		:addClass('etytree-body')
		:node(final_tree)

	return tostring(html_create('div')
		:addClass('etytree NavFrame')
		:attr('data-etytree-height', final_height)
		:attr('data-etytree-width', final_width)
		:tag('div')
		:addClass('NavHead')
		:tag('div')
		:wikitext('නිරුක්ති ශාඛා')
		:done()
		:done()
		:tag('div')
		:addClass('NavContent')
		:node(container)
		:done())
end

return export
"https://si.wiktionary.org/w/index.php?title=Module:etymon/tree&oldid=227076" වෙතින් සම්ප්‍රවේශනය කෙරිණි