Module:Navigation
Documentation for this module may be created at Module:Navigation/doc
local function trim(s)
return s:match "^%s*(.-)%s*$"
end
-- Supported line format: ' ## Page Title '
-- or: ' ### [[Page Link ]] extra text '
function parse_line2(line)
return line:match '^%s*(#+)%s*%[*%s*([^%]]*[^%s%]]+)%s*%]*%s*.*$'
end
local function read_tree_lines(lines)
local res = {lines={}, cur=0}
for nr=1,#lines do
repeat
-- mw.log('line ' .. nr .. ': <' .. lines[nr] ..'>')
level, title = parse_line2(lines[nr])
if level == nil then
break
else
level = #level
end
res.lines[#res.lines + 1] = {level=level, title=title}
until true
end
return res
end
local function next(nodes)
if #nodes.lines > nodes.cur then
nodes.cur = nodes.cur + 1
local pair = nodes.lines[nodes.cur]
return pair.level, pair.title
else
return -1, ''
end
end
local function read_tree(tree, level, title, nodes)
local cur_level = level
while cur_level >= 0 and cur_level >= level do
repeat
if cur_level == level then
tree.children[#tree.children + 1] = {content=title, children={}}
else
cur_level, title = read_tree(tree.children[#tree.children], level+1, title, nodes)
break
end
cur_level, title = next(nodes)
until true
end
return cur_level, title
end
local function visit_tree(tree, visitor)
if visitor.enter then
visitor:enter(tree)
end
for i=1, #tree.children do
repeat
local child = tree.children[i]
if visitor.filter and not visitor:filter(child) then
break
end
visitor:visit(child)
if #child['children'] > 0 then
visit_tree(child, visitor)
end
until true
end
if visitor.leave then
visitor:leave(tree)
end
end
local Renderer = { parents={}, titles = {} }
function Renderer:enter(tree)
self.parents[#self.parents + 1] = self.parents[#self.parents]:tag 'ol'
self.titles[#self.titles + 1] = tree.content
end
function Renderer:visit(tree)
local li = self.parents[#self.parents]:tag 'li'
li:wikitext('[['..tree.content..']]')
end
function Renderer:leave(tree)
self.parents[#self.parents] = nil
self.titles[#self.titles] = nil
end
local CurrentFinder = { titles={}, result = nil }
function CurrentFinder:enter(tree)
if tree.content then
self.titles[#self.titles + 1] = tree.content
end
end
function CurrentFinder:visit(tree)
if tree.content == self.target then
self.result = {}
for i = 1,#self.titles do
self.result[#self.result + 1] = self.titles[i]
end
self.result[#self.result + 1] = tree.content
end
end
function CurrentFinder:leave(tree)
self.titles[#self.titles] = nil
end
local function find_current_node(tree, title)
CurrentFinder.parents = {}
CurrentFinder.target = title
CurrentFinder.result = nil
visit_tree(tree, CurrentFinder)
end
local function render_tree(tree, current_node)
local div = mw.html.create('div')
div:wikitext('Navigation')
div:cssText('border: 1px solid blue; background-color: #f0f0f0')
Renderer.parents = {div}
Renderer.filter = function (self, tree)
--[[
mw.log('======\nrender filter: ')
mw.log("self titles:" ..mw.dumpObject(self.titles))
mw.log("current titles: " ..mw.dumpObject(current_node))
mw.log("tree: " ..mw.dumpObject(tree))
]]
if #self.titles > #current_node then
return false
end
for i=1,#self.titles do
if self.titles[i] ~= current_node[i] then
return false
end
end
return true
end
visit_tree(tree, Renderer)
return tostring(div)
end
local function links_from_nodes(nodes, current_title)
local level, title = next(nodes)
local tree = {content='', children={}}
read_tree(tree, level, title, nodes)
find_current_node(tree, current_title)
local current_node = CurrentFinder.result
if current_node == nil then
current_node = {current_title}
mw.log(current_title .. ' not found in page')
end
return render_tree(tree, current_node)
end
-- Test
--[[
local tree = {content='', children={
{content='a1', children={
{content='a1-b1', children={}},
{content='a1-b2', children={
{content='a1-b2-c1', children= {
{content='a2-b2-c1-d1', children={}}
}}
}}
}},
{content='a2', children= {
{content='a2-b2', children={}},
{content='a2-c2', children={}}
}}
}}
]]--
local p = {}
function p.hello(frame)
mw.log(mw.dumpObject(frame))
return "Hello, world!"
end
function p.test(x)
return x+1
end
local function parse_contents(contents_page)
local frame = mw.getCurrentFrame()
local title = mw.title.new(contents_page)
mw.log('fetch contents of title: ' .. title.text)
local content = title:getContent()
if content == nil or #content == 0 then
return ''
end
local function get_lines(text)
if text:sub(-1)~="\n" then
text = text .. "\n"
end
return text:gmatch("(.-)\n")
end
local lines = {}
--[[
for line in get_lines(content) do
lines[#lines+1] = frame:preprocess(line)
end
]]
for line in get_lines(frame:preprocess(content)) do
lines[#lines+1] = line
end
return lines
end
local function get_params(frame)
local current_title = frame.args['selfpage'] or frame:preprocess '{{PAGENAME}}'
mw.log('Current title: ' .. current_title)
-- mw.logObject(frame)
local contents_page = frame.args[1] or nil
if not contents_page then
parent = frame:getParent()
contents_page = mw.title.new(parent:getTitle()).rootPageTitle.text .. "/Contents"
end
mw.log('Contents page name: ' .. (contents_page or '<name missing>'))
return current_title, contents_page
end
-- prev and next links
function p.create_prevnext(frame)
local current_title, contents_page = get_params(frame)
local lines = parse_contents(contents_page)
local nodes = read_tree_lines(lines)
local level, title = next(nodes)
local tree = {content='', children={}}
read_tree(tree, level, title, nodes)
find_current_node(tree, current_title)
local current_node = CurrentFinder.result
local tree_lines = nodes.lines
title_idx = nil
mw.log("# tree lines: " .. #tree_lines)
for i = 1, #tree_lines do
title = tree_lines[i].title
if title == current_title then
title_idx = i
end
end
mw.log("Title index: " .. (title_idx or "<title not found>"))
if title_idx == nil then
return frame:preprocess('{{Note|Page title ' .. current_title .. ' missing from ' .. name .. '|error}}')
end
local res = '{| class="navcontrol" style="margin-bottom:1em; padding:0.1em; 100%; width: 100%; text-align: left; background-color: #f0f0f0;"\n'
if title_idx > 1 then
res = res .. "|style='text-align:left'|'''◀''' [[" .. tree_lines[title_idx-1].title .. "]]\n"
end
res = res .. "|style='text-align:center'| [[" .. current_node[2] .. ']]'
for i = 3, #current_node do
res = res .. ' > [[' .. current_node[i] .. ']]'
end
res = res .. '\n'
if title_idx < #tree_lines then
res = res .. "|style='text-align:right'| [[" .. tree_lines[title_idx+1].title .. "]] '''▶'''\n"
end
-- if #CurrentFinder.result > 1 then
-- local parent = CurrentFinder.result[#CurrentFinder.result-1]
-- if #parent > 0 then
-- res = res .."||'''↑ Up''' [[" .. parent .."]] "
-- end
-- end
res = res .. "\n|}\n"
-- return log .. res
return res
end
function p.create_sidebar_tree(frame)
local current_title, contents_page = get_params(frame)
local lines = parse_contents(contents_page)
--mw.logObject(lines)
local nodes = read_tree_lines(lines).lines
--mw.logObject(nodes)
local res = '{{#tree:\n'
for i=1,#nodes do
--mw.logObject(nodes[i])
for i = 1, nodes[i].level do
res = res .. '*'
end
res = res .. nodes[i].title .. '\n'
end
res = res .. '}}'
--mw.log(res)
return frame:preprocess(res)
--return res
end
function p.create_toc(frame)
local current_title, contents_page = get_params(frame)
mw.log(current_title)
local lines = parse_contents(contents_page)
local nodes = read_tree_lines(lines)
return links_from_nodes(nodes, current_title)
end
--[[ How to test in Debug Console (no page saves required)
f = mw.getCurrentFrame():newChild{title="Debug", args={'Test toc', selfpage='Page3'}}
=p.create_links(f)
f = mw.getCurrentFrame():newChild{title="Manual/GettingStarted/Introduction", args={"Manual/Contents", selfpage="Manual/GettingStarted/Introduction"}}
]]
p.create_links = p.create_prevnext
return p