Borderlands Wiki
Borderlands Wiki
8066
страниц
мНет описания правки
мНет описания правки
(не показаны 34 промежуточные версии этого же участника)
Строка 3: Строка 3:
 
--local getArgs = require("Dev:Arguments").getArgs
 
--local getArgs = require("Dev:Arguments").getArgs
 
local p = {}
 
local p = {}
 
function p.tlm1 (frame)
 
--template:tlocmission
 
 
local args = frame.args
 
local tfull = args['1'] or args[1] or 'В этом разделе указаны задания, связанные с данной локацией. В разделе "Выдаются" указаны задания, которые берут начало в этой локации; а в разделе "Выполняются" находятся задания, для которых данная локация служит промежуточной или конечной.'
 
local tin = args['2'] or args[2] or 'В этом разделе указаны задания, которые берут начало в этой локации.'
 
local tout = args['3'] or args[3] or 'В этом разделе указаны задания, для которых данная локация служит промежуточной или конечной.'
 
local nret = args['4'] or args[4] or tfull -- default
 
local ntitle = mw.title.new((args.p and #args.p > 0) and args.p or mw.title.getCurrentTitle().text)
 
local mode = args.mode or '' -- auto для автосписка миссий
 
local game = mw.text.trim(args.game or '') -- игра
 
game = #game > 0 and game or nil
 
 
if mode == 'auto' then
 
-- автоматический список миссий
 
local nret1 = {}
 
local dplText = '{{#dpl:namespace=|uses=template:Задание|linksto=%PAGENAME1%|include={Задание}::Локация взятия:Локация выполнения:Игра|includematch=/%PAGENAME2%/s|includetrim=true|mode=userformat|tablerow=~~%PAGE%,~%%,~%%,~%%|noresultsheader=|noresultsfooter=|suppresserrors=true|allowcachedresults=true}}'
 
local tnoresult = 'В этом разделе были бы указаны задания, связанные с данной локацией. Если бы их удалось найти.'
 
local there = ' (выполняется здесь же)'
 
-- дополнительные миссии (которые невозможно найти автоматически)
 
-- формат: миссия1~~миссия2
 
local addStart = tableTrim(mw.text.split(args.astart or '', '~~'))
 
local addEnd = tableTrim(mw.text.split(args.aend or '', '~~'))
 
-- миссии, которые начинаются\заканчиваются здесь
 
local mstart = addStart
 
local mend = addEnd
 
-- defend dpl against stuped chars in the title (<>)
 
local linksto = ntitle.text:gsub('[«<>»]', '%')
 
local includematch = ntitle.text:gsub('([()])', '\\%1'):gsub('[«<>»]', '.*?'):gsub('[ _]', '[ _]')
 
dplText = dplText:gsub('%%PAGENAME1%%', linksto):gsub('%%PAGENAME2%%', includematch)
 
local dplResult = frame:preprocess(dplText)
 
if 1 then return frame:preprocess('<pre>'..dplText..'\n\n'..dplResult..'</pre>')end
 
nret = tnoresult
 
 
if dplResult and #dplResult > 0 then
 
-- do stuff
 
-- parse dpl output
 
-- ~~title (mission)|~loc1|~loc2|~game
 
local dplLines = mw.text.split(dplResult, '~~')
 
-- remove 1st empty row (cuz ~~data -> {, data})
 
table.remove(dplLines, 1)
 
function findLoc(locs, loc)
 
-- find loc in locs
 
-- returns loc index or nil
 
for i, v in ipairs(locs) do
 
if v.link == loc.link then
 
return i
 
end
 
end
 
return nil
 
end--findloc
 
 
for _, line in ipairs(dplLines) do
 
local mis, loc1, loc2, gam = line:match('^[%s%c~]*(.-)[%s%c]*|~(.-)[%s%c]*|~(.-)[%s%c]*|~(.-)[%s%c]*$')
 
-- фильтр по игре
 
if gam and #gam ~= 0 then
 
gam = mw.text.trim(parseLinks(gam, 'link')[1])-- only 1 game expected is
 
if #gam == 0 then gam = nil end
 
else
 
gam = nil
 
end
 
if (not gam or not game) or (gam and game and gam == game) then
 
loc1 = parseLinks(loc1, 'table')
 
loc2 = parseLinks(loc2, 'table')
 
-- локация выполнения
 
for i, v in ipairs(loc2) do
 
local isInLoc1 = findLoc(loc1, v)
 
-- миссия ссылается на текущую страницу?
 
if v.link == ntitle.text then
 
if isInLoc1 then
 
-- лока сдачи совпадает с локой взятия
 
loc1[isInLoc1].here = true
 
table.remove(loc2, i)
 
break
 
end
 
table.insert(mend, '[[' .. mis .. ']]')
 
end
 
end-- for loc2
 
-- если лока сдачи не указана, то = локе взятия
 
-- здесь нельзя определить конкретную локу из списка, потому - первая
 
if #(loc2 or {}) == 0 then loc1[1].here = true end
 
-- локация взятия
 
for i, v in ipairs(loc1) do
 
-- миссия ссылается на текущую страницу?
 
if v.link == ntitle.text then
 
table.insert(mstart, '[[' .. mis .. ']]' .. (v.here and there or ''))
 
break
 
end
 
end-- for loc1
 
end-- if gam==game
 
end
 
end-- if dplresult
 
-- process (sort etc) tables here
 
table.sort(mstart)
 
table.sort(mend)
 
-- strip empty values
 
table.insert(mstart, 1, '\n*Выдаются:')
 
table.insert(mend, 1, '\n*Выполняются:')
 
mstart = #mstart > 1 and table.concat(mstart, '\n**') or nil
 
mend = #mend > 1 and table.concat(mend, '\n**') or nil
 
-- какую надпись показать?
 
if mstart then
 
nret = mend and tfull or tin
 
else
 
nret = mend and tout or tnoresult
 
end
 
table.insert(nret1, nret)
 
table.insert(nret1, dplText)
 
table.insert(nret1, dplResult)
 
table.insert(nret1, mstart)
 
table.insert(nret1, mend)
 
return frame:preprocess('<pre>' .. table.concat(nret1, '\n----') .. '</pre>')
 
--return table.concat(nret1)
 
else
 
-- заполнено вручную
 
local ncontent = ntitle:getContent()
 
local npattern = '==Задания==.-'
 
local findresult1, findresult2 = nil, nil
 
findresult1 = mw.ustring.find(ncontent, npattern..'\n%*Выдаются', 1, false)
 
findresult2 = mw.ustring.find(ncontent, npattern..'\n%*Выполняются', 1, false)
 
if (findresult1 and findresult2) then
 
nret = tfull
 
elseif findresult1 then
 
nret = tin
 
elseif findresult2 then
 
nret = tout
 
end
 
return nret
 
end-- if mode==auto
 
end-- tlm
 
   
 
function p.hello( frame )
 
function p.hello( frame )
Строка 240: Строка 109:
 
local alias = args['2'] or args[2]
 
local alias = args['2'] or args[2]
 
local default = args['3'] or args[3]
 
local default = args['3'] or args[3]
if not lang then return '' end
+
-- new page = no content
  +
if (not lang) or (not content) then return (alias or default) end
 
local s, re, c1st, mtch
 
local s, re, c1st, mtch
 
c1st = mw.ustring.sub(lang, 1, 1)
 
c1st = mw.ustring.sub(lang, 1, 1)
re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^|]-)%]%]'
+
re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^%]]-)%]%]'
 
mtch = mw.ustring.match(content, re)
 
mtch = mw.ustring.match(content, re)
  +
mtch = mtch and mw.text.split(mtch, '|')[1] or nil
 
if #mw.text.trim(alias or '') == 0 then
 
if #mw.text.trim(alias or '') == 0 then
 
alias = mtch
 
alias = mtch
Строка 256: Строка 127:
 
--1: lang
 
--1: lang
 
local title = mw.title.getCurrentTitle()
 
local title = mw.title.getCurrentTitle()
  +
-- do not try to process new page
  +
if not title then return end
 
local content = title:getContent()
 
local content = title:getContent()
  +
if not content then return end
 
local args = frame.args
 
local args = frame.args
 
local lang = args['1'] or args[1]
 
local lang = args['1'] or args[1]
Строка 262: Строка 136:
 
local re, c1st
 
local re, c1st
 
c1st = mw.ustring.sub(lang, 1, 1)
 
c1st = mw.ustring.sub(lang, 1, 1)
re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^|]-)%]%]'
+
re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^%]]-)%]%]'
return mw.ustring.match(content, re)
+
re = mw.ustring.match(content, re)
  +
re = re and mw.text.split(re, '|')[1] or nil
 
return re
 
end --hasinterwiki
 
end --hasinterwiki
   
Строка 528: Строка 404:
   
 
function splitLink(link)
 
function splitLink(link)
  +
-- раздирает ссылки на части
 
local al = mw.text.split(link, '|')
 
local al = mw.text.split(link, '|')
 
if part == 'link' then
 
if part == 'link' then
Строка 546: Строка 423:
 
end-- splitlink
 
end-- splitlink
   
  +
function parseLinkHelper(t)
for l in data:gmatch(patLink) do
 
  +
-- нужна для избежания дублирования кода,
l = splitLink(l)
 
  +
-- если на выпарсивание ссылок передали массив строк
if l then table.insert(links, l) end
 
  +
-- пользуется переменными родителя
end
 
if #links == 0 then
+
local ret = {}
 
for l in t:gmatch(patLink) do
-- no links (loc w\o [[]]: sanctuary instead of [[sanctuary]])
 
data = splitLink(data)
+
l = splitLink(l)
table.insert(links, data)
+
if l then table.insert(ret, l) end
end
+
end
 
if #ret == 0 then
 
-- no links (loc w\o [[]]: sanctuary instead of [[sanctuary]])
 
t = splitLink(t)
 
table.insert(ret, t)
 
end
 
return ret
  +
end-- parselinkhelper
  +
 
if type(data) == 'table' then
  +
-- пришёл массив строк
 
for _, v in pairs(data) do
  +
-- все найденные ссылки собираются на одном уровне
 
for _, v1 in pairs(parseLinkHelper(v)) do
 
table.insert(links, v1)
 
end
 
end
 
else
  +
links = parseLinkHelper(data)
 
end-- if data==table
  +
 
return links
 
return links
 
end-- parselinks
 
end-- parselinks
Строка 571: Строка 468:
 
return tab
 
return tab
 
end-- tabletrim
 
end-- tabletrim
  +
  +
function tableSort(el1, el2)
  +
-- sane table sorting alg ("str" is lesser than "str - str")
 
-- strip []
  +
el1 = mw.ustring.gsub(el1, '[%[%]]', '')
  +
el2 = mw.ustring.gsub(el2, '[%[%]]', '')
  +
  +
if (#el1 < #el2) and (mw.ustring.sub(el2, 1, #el1) == el1) then
  +
-- if el2 includes el1, then el is lesser
 
return el1
  +
elseif (#el2 < #el1) and (mw.ustring.sub(el1, 1, #el2) == el2) then
  +
-- if el1 includes el2, then el2 is lesser
 
return el2
 
else
  +
-- otherwise: just <
 
return el1 < el2
 
end
  +
end-- tablesort
   
 
function p.tlm (frame)
 
function p.tlm (frame)
 
--template:tlocmission
 
--template:tlocmission
   
local args = frame.args
+
local args = frame.args or frame
 
local tfull = args['1'] or args[1] or 'В этом разделе указаны задания, связанные с данной локацией. В разделе "Выдаются" указаны задания, которые берут начало в этой локации; а в разделе "Выполняются" находятся задания, для которых данная локация служит промежуточной или конечной.'
 
local tfull = args['1'] or args[1] or 'В этом разделе указаны задания, связанные с данной локацией. В разделе "Выдаются" указаны задания, которые берут начало в этой локации; а в разделе "Выполняются" находятся задания, для которых данная локация служит промежуточной или конечной.'
 
local tin = args['2'] or args[2] or 'В этом разделе указаны задания, которые берут начало в этой локации.'
 
local tin = args['2'] or args[2] or 'В этом разделе указаны задания, которые берут начало в этой локации.'
Строка 588: Строка 503:
 
-- автоматический список миссий
 
-- автоматический список миссий
 
local nret1 = {}
 
local nret1 = {}
local dplText = '{{#dpl:namespace=|uses=template:Задание|linksto=%PAGENAME%|include={Задание}::Локация взятия:Локация выполнения:Игра|includematch=/%PAGENAME%/s|includetrim=true|mode=userformat|tablerow=~~%PAGE%,~%%,~%%,~%%|noresultsheader=|noresultsfooter=|suppresserrors=true|allowcachedresults=true}}'
+
local dplText = '{{#dpl:namespace=|uses=template:Задание|linksto=%PAGENAME1%|include={Задание}::Локация взятия:Локация выполнения:Игра|includematch=/%PAGENAME2%/s|includetrim=true|mode=userformat|tablerow=~~%PAGE%,~%%,~%%,~%%|noresultsheader=|noresultsfooter=|suppresserrors=true|allowcachedresults=true}}'
 
local tnoresult = 'В этом разделе были бы указаны задания, связанные с данной локацией. Если бы их удалось найти.'
 
local tnoresult = 'В этом разделе были бы указаны задания, связанные с данной локацией. Если бы их удалось найти.'
 
local there = ' (выполняется здесь же)'
 
local there = ' (выполняется здесь же)'
Строка 598: Строка 513:
 
local mstart = addStart
 
local mstart = addStart
 
local mend = addEnd
 
local mend = addEnd
 
-- defend dpl against stupid chars in the title (<>)
dplText = dplText:gsub('%%PAGENAME%%', ntitle.text)
 
  +
-- вызов всегда должен быть через mw.ustring, чтобы получить ustring вместо string
local dplResult = frame:preprocess(dplText)
 
  +
-- баг?: ustring должен быть на каждой строке по дефолту
 
local linksto = mw.ustring.gsub(ntitle.text, '[«<>»]', '%')
 
local includematch = mw.ustring.gsub(ntitle.text, '([()/])', '\\%1')
  +
-- .*?: потенциально фейловый паттерн: может гребануть больше, чем надо
  +
-- на практике пока не встречалось
  +
includematch = mw.ustring.gsub(includematch, '[«<>»]', '.*?')
  +
includematch = mw.ustring.gsub(includematch, '[ _]', '[ _]')
 
dplText = mw.ustring.gsub(dplText, '%%PAGENAME1%%', linksto)
 
dplText = mw.ustring.gsub(dplText, '%%PAGENAME2%%', includematch)
  +
-- тестовые данные; не предназначены для полной проверки без указания параметра p (плохой формат, -список миссий)
  +
local dplResult = frame.preprocess and frame:preprocess(dplText) or [=[~~Всемогущий повелитель |~Дожигатель |~[[Дожигатель]] и [[Упавший Гелиос]] |~Borderlands 2~~Друзья на всю жизнь (задание) |~Дожигатель |~[[Развалины Даль]], [[Норы]], [[Упавший Гелиос]] и [[Исследовательский центр "Скарабей"]] |~Borderlands 2~~Защитник (задание) |~Дожигатель |~Развалины Даль |~Borderlands 2~~Клятва Гиппократа |~Дожигатель |~Развалины Даль |~Borderlands 2~~Клятва Гиппократа - Глава 2 |~Дожигатель |~Норы |~Borderlands 2~~Концертная приманка |~Дожигатель |~Гудящая Бездна |~Borderlands 2~~Космический ковбой |~Дожигатель |~Развалины Даль |~Borderlands 2~~Мой хрупкий пони |~Дожигатель |~Упавший Гелиос |~Borderlands 2~~Охотник Вон |~Дожигатель |~Норы |~Borderlands 2~~Поиск образцов |~Дожигатель |~Развалины Даль |~Borderlands 2~~Правитель Вон |~Дожигатель |~Развалины Даль |~Borderlands 2~~Сиренология |~Дожигатель |~Упавший Гелиос |~Borderlands 2~~Точка преткновения |~Дожигатель |~Норы |~Borderlands 2]=]
 
nret = tnoresult
 
nret = tnoresult
   
Строка 607: Строка 533:
 
-- ~~title (mission)|~loc1|~loc2|~game
 
-- ~~title (mission)|~loc1|~loc2|~game
 
local dplLines = mw.text.split(dplResult, '~~')
 
local dplLines = mw.text.split(dplResult, '~~')
  +
-- распарсенный список "выполняющихся" миссий
  +
-- в связи с особенностями заполнения шаблона делать такое же
  +
-- для "выдающихся" смысла пока что нет
 
local addEndParsed = parseLinks(addEnd, 'table')
 
-- remove 1st empty row (cuz ~~data -> {, data})
 
-- remove 1st empty row (cuz ~~data -> {, data})
 
table.remove(dplLines, 1)
 
table.remove(dplLines, 1)
Строка 629: Строка 559:
 
gam = nil
 
gam = nil
 
end
 
end
  +
if (not gam or not game) or (gam and game and gam == game) then
 
  +
-- эта миссия уже была добавлена вручную?
 
local handmade = findLoc(addEndParsed, {link = mis})
 
 
if not handmade and ((not gam or not game) or (gam and game and gam == game)) then
 
--mw.log(loc1, loc2)
 
loc1 = parseLinks(loc1, 'table')
 
loc1 = parseLinks(loc1, 'table')
 
loc2 = parseLinks(loc2, 'table')
 
loc2 = parseLinks(loc2, 'table')
Строка 635: Строка 570:
 
for i, v in ipairs(loc2) do
 
for i, v in ipairs(loc2) do
 
local isInLoc1 = findLoc(loc1, v)
 
local isInLoc1 = findLoc(loc1, v)
-- миссия ссылается на текущую страницу?
 
 
if v.link == ntitle.text then
 
if v.link == ntitle.text then
 
if isInLoc1 then
 
if isInLoc1 then
-- лока сдачи совпадает с локой взятия
+
-- если лока сдачи совпадает с локой взятия,
 
-- то установить here
 
loc1[isInLoc1].here = true
 
loc1[isInLoc1].here = true
 
table.remove(loc2, i)
 
table.remove(loc2, i)
Строка 658: Строка 593:
 
end-- for loc1
 
end-- for loc1
 
end-- if gam==game
 
end-- if gam==game
end
+
end-- for line in dpllines
 
end-- if dplresult
 
end-- if dplresult
 
-- process (sort etc) tables here
 
-- process (sort etc) tables here
table.sort(mstart)
+
table.sort(mstart, tableSort)
table.sort(mend)
+
table.sort(mend, tableSort)
 
-- strip empty values
 
-- strip empty values
 
table.insert(mstart, 1, '\n*Выдаются:')
 
table.insert(mstart, 1, '\n*Выдаются:')
Строка 679: Строка 614:
 
table.insert(nret1, mstart)
 
table.insert(nret1, mstart)
 
table.insert(nret1, mend)
 
table.insert(nret1, mend)
--return frame:preprocess('<pre>' .. table.concat(nret1, '\n----') .. '</pre>')
+
--return frame.preprocess and frame:preprocess('<pre>' .. table.concat(nret1, '\n----') .. '</pre>') or table.concat(nret1, '\n----')
 
return table.concat(nret1)
 
return table.concat(nret1)
 
else
 
else
Строка 771: Строка 706:
 
end
 
end
 
end
 
end
  +
  +
-- экспорт фигни для отладки
  +
p.parseLinks = parseLinks
  +
p.tableTrim = tableTrim
  +
p.tableSort = tableSort
   
 
return p
 
return p

Версия от 17:53, 8 марта 2020

Для документации этого модуля может быть создана страница Модуль:Nutils/doc

--<syntaxhighlight lang="lua">
--local hasc = require('Dev:Pageinfo')
--local getArgs = require("Dev:Arguments").getArgs
local p = {}

function p.hello( frame )
    local nt = ''
    nt = nt .. '<div contenteditable="true" id="el1" tabindex="0">element1</div>'
    return nt --"Hello, world!"
end --hello

function p.tst (frame)
    local args = frame.args or frame
    return p.hasbit(3, p.bit(1))
end --tst

function p.tst2 (frame)
    local arg1 = frame.args[1]
    local ns = ''
    local ns1 = ''
    --ns = '[[' .. args[1] .. ']]'
    --ns = arg1
    --ns = frame:newParserValue(ns):expand()
    --ns = frame:preprocess(ns)
    for i = 1, mw.ustring.len(ns) do
        ns1 = ns1..'_'..mw.ustring.sub(ns, i, i)
    end
    ns = ns .. ns1
    return ns
end --tst2

function p.rainbow (frame)
    -- draws rainbow text
    if not frame or not frame.args then return end
    -- debug console defence
    -- get frame.args if arg[1] or frame:parent.args
    local args = frame.args
    if not args['1'] or not args[1] then
        args = (frame.getParent and frame:getParent() or frame).args
    end
    local text = args['1'] or args[1]
    if not text then return '' end
    local ret = {}-- table is fastest way to deal with strings
    local linkIndex, _, linkHead, linkLink, linkAlias, linkTail = text:find('(.-)%[%[([^]]-)|([^]]+)%]%](.*)', 1, false)
    if linkIndex then
        table.insert(ret, linkHead or '')
        table.insert(ret, '[[')
        table.insert(ret, linkLink)
        table.insert(ret, '|')
        table.insert(ret, rainbowize(linkAlias))
        table.insert(ret, ']]')
        table.insert(ret, linkTail or '')
    else
        return rainbowize(text)
    end
    return table.concat(ret)
end-- rainbow

function rainbowize (text)
    -- rainbowize the text
    if not text then return '' end
    -- color array: red, orange, yellow, greenyellow, cyan, deepskyblue, violet
    local rainbow = {'#ff0000', '#ffa500', '#ffff00', '#adff2f', '#00ffff', '#00bfff', '#ee82ee'}
    local spanHead = '<span style="color:'
    local spanHeadC = '">'
    local spanTail = '</span>'
    local ret = {}-- table is fastest way to deal with strings
    for index, character in ipairs(mw.text.split(text, '')) do
        local pos = math.fmod(index, #rainbow)
        -- if pos == 0 (last element of the table) then return last el
        pos = pos ~= 0 and pos or #rainbow
        table.insert(ret, spanHead)-- open span
        table.insert(ret, rainbow[pos])-- add color
        table.insert(ret, spanHeadC)-- close opening
        table.insert(ret, character)-- add character
        table.insert(ret, spanTail)-- close span
    end
    return table.concat(ret)
end-- rainbowize

function p.setCatLocation (frame)
    --set category by location
    --1: location: location
    --2: prefix: prefix
    --3: postfix: postfix
    local args = frame.args or frame
    local location = args.location or args['1'] or args[1]
    if (not location) or (#location == 0) then return end
    local prefix = args.prefix or args['2'] or args[2] or ''
    local postfix = args.postfix or args['3'] or args[3] or ''
    --only 1 location permitted is
    local s = mw.ustring.find(location, ',') or mw.ustring.find(location, '%]%].+%]%]')
    if s then return end
    --remove []
    location = mw.ustring.match(location, '%[*([^%]|]+)%]*')
    if not location then return end
    location = '[[category:' .. prefix .. location .. postfix .. ']]'
    return frame.preprocess and frame:preprocess(location) or location
end--setcatlocation

function p.getInterwiki (frame)
    --returns interwiki link for lang
    --format: lang:(link or default)|alias
    --1: lang; 2: alias; 3: default link
    local title = mw.title.getCurrentTitle()
    local content = title:getContent()
    local args = frame.args
    local lang = args['1'] or args[1]
    local alias = args['2'] or args[2]
    local default = args['3'] or args[3]
    -- new page = no content
    if (not lang) or (not content) then return (alias or default) end
    local s, re, c1st, mtch
    c1st = mw.ustring.sub(lang, 1, 1)
    re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^%]]-)%]%]'
    mtch = mw.ustring.match(content, re)
    mtch = mtch and mw.text.split(mtch, '|')[1] or nil
    if #mw.text.trim(alias or '') == 0 then
        alias = mtch
    end
    s = '[[:' .. lang .. ':' .. (mtch or default) .. '|' .. (alias or default) .. ']]'
    return s
end --getinterwiki

function p.hasInterwiki (frame)
    --returns link if page has lang interwiki
    --1: lang
    local title = mw.title.getCurrentTitle()
    -- do not try to process new page
    if not title then return end
    local content = title:getContent()
    if not content then return end
    local args = frame.args
    local lang = args['1'] or args[1]
    if not lang then return end
    local re, c1st
    c1st = mw.ustring.sub(lang, 1, 1)
    re = '%[%[[' .. mw.ustring.upper(c1st) .. mw.ustring.lower(c1st) .. ']' .. mw.ustring.lower(mw.ustring.sub(lang, 2)) .. ':([^%]]-)%]%]'
    re = mw.ustring.match(content, re)
    re = re and mw.text.split(re, '|')[1] or nil
    return re
end --hasinterwiki

function p.ifex (frame)
    --returns: {{template:name|p1|p2|p3}} or [[page]] or [[:category:name]] or pa1
    --opt is a bitwise container
    --name/1: pagename, opt: options
    local args = frame.args or frame
    local name = args['name'] or args['1'] or args[1] or ''
    local ns = args['ns'] or '' --namespace
    local nsn = '' --namespaced name
    local alt = args['alt'] or '' --alt text
    local tail = args['tail'] or '' --tail
    if tail ~= '' then
        tail = ' ' .. tail
    end
    --opt: 1: do not search for category; 2: do not return {{{1}}}; 3: both
    local opt = tonumber(args['opt']) or 0
    --parent args
    local parent
    if frame and frame.getParent then
        parent = frame:getParent()
    else
        parent = frame
    end
    --local parent = p.iif(frame.getParent, frame:getParent(), frame)
    local pargs = parent.args or parent
    local pa1, pa2, pa3 = pargs['1'] or pargs[1] or '', pargs['2'] or pargs[2] or '', pargs['3'] or pargs[3] or ''
    -- [[{{san|pa1}}|{{san|pa1|2}}]] {{san|pa1|3}} --nothing found, make red link
    local ret = name .. tail
    --'{{ifexistc|{{san|1=' .. pa1 .. '}}|[[:Категория:{{san|1=' .. pa1 .. '}}|{{san|1=' .. pa1 .. '|2=2}}]] {{san|1=' .. pa1 .. '|2=3}}|' .. pa1 .. '}}'
    --params
    local p1, p2, p3 = args['p1'] or '', args['p2'] or '', args['p3'] or ''
    --local ret = isfalse
    if p.hasbit(opt, p.bit(2)) then --do not return {{{1}}}
        ret = ''
    end
    if name ~= '' then
        --capitalize name
        name = mw.ustring.upper(mw.ustring.sub(name, 1, 1)) .. mw.ustring.sub(name, 2)
        --normalize
        name = mw.ustring.gsub(name, '_', ' ')
        if ns ~= '' then nsn = mw.ustring.upper(mw.ustring.sub(ns, 1, 1)) .. mw.ustring.sub(ns, 2) .. ':' .. name end
        local data = mw.loadData('module:Nbigbigdatabig')
        if nsn ~= '' and data[nsn] then 
            if ns == 'Шаблон' or ns == 'Template' then -- template
                --ret = {{nsn|{{{1|}}}|{{{2|}}}|{{{3|}}}}}
                ret = '{{' .. nsn .. '|1=' .. p1 .. '|2=' .. p2 .. '|3=' .. p3 .. '}}' .. tail
            else
                ret = '[[' .. nsn .. p.iif(alt~='', '|' .. alt, '') .. ']]' .. tail
            end
        elseif data[name] then --page in ns:0
            --ret = '[[{{san|{{{1}}}}}|{{san|{{{1}}}|2}}]] {{san|{{{1}}}|3}}'
            --ret = '[[{{san|1=' .. pa1 .. '}}|{{san|1=' .. pa1 .. '|2=2}}]] {{san|1=' .. pa1 ..'|2=3}}'
            ret = '[[' .. name .. p.iif(alt~='', '|' .. alt, '') .. ']]' .. tail
        elseif not p.hasbit(opt, p.bit(1)) then --category
            local scat = p.ifexc({[1]=name, [2]=alt, [3]=tail, ['opt']=1})
            if scat ~= '' then
                ret = scat
            end
        end
    end
    if frame and frame.preprocess then
        return frame:preprocess(ret)
    else
        return ret
    end
end --ifex

function p.ifexc (frame)
    --ifexist for category
    --opt is a bitwise container
    --returns [[:category:name|alt]] tail or name or '' if opt:1
    --or [[catefory:name|alt]] tail if opt:2
    local args = frame.args or frame
    local name = args['name'] or args['1'] or args[1] or ''
    local alt = args['alt'] or args['2'] or args[2] or ''
    local tail = args['tail'] or args['3'] or args[3] or ''
    if tail ~= '' then
        tail = ' ' .. tail
    end
    --opt: 1: do not return {{{1}}}; 2: set cat [[cat:...]]
    local opt = tonumber(args['opt']) or 0
    local ret = ''
    if p.hasbit(opt, p.bit(1)) then
        ret = ''
    else
        if p.hasbit(opt, p.bit(2)) then
            ret = 'Категория:' .. name .. tail
        else
            ret = name .. tail
        end
    end
    if name ~= '' then
        --capitalize name
        name = mw.ustring.upper(mw.ustring.sub(name, 1, 1)) .. mw.ustring.sub(name, 2)
        --normalize
        name = mw.ustring.gsub(name, '_', ' ')
        local data = mw.loadData('module:nbigdatacat')
        if data[name] then
            if p.hasbit(opt, p.bit(2)) then
                ret = '[[Категория:' .. name .. p.iif(alt~='', '|' .. alt, '') .. ']]' .. tail
            else
                ret = '[[:Категория:' .. name .. p.iif(alt~='', '|' .. alt, '') .. ']]' .. tail
            end
        end
    end
    if frame and frame.preprocess then
        return frame:preprocess(ret)
    else
        return ret
    end
end

function p.th (frame)
    --transclude header
    --according to template:th --excepts 1st param (t) -> params shifted
    
    local args = frame.args
    local ns, ncontent, npattern = ''
    local ncount = tonumber(args['c'] or args[3]) or 0
    local ntitle = args['d'] or args[1]
    --ncount = tonumber(ncount)
    --if type(args['t']) ~= 'nil' then ns=args['t'] elseif type(args[1]) ~= 'nil' then ns=args[1] end
    if ((args['bylink'] or args[5] or '1') == '0') then
        ncontent = (ntitle)
    else
        ncontent = frame:expandTemplate{ title=':' .. ntitle }
    end
    npattern = (args['h'] or args[2] or 'Описание')
    if ncount == 0 then
        npattern = '=+' .. npattern .. '=+(.-)=='
    else
        npattern = '=+' .. npattern .. '.-%.-=*(' .. mw.ustring.rep('[%w%s,:;%-%+%<%>]+[%<%>%.%?%!%C]+', ncount) .. ')'
    end
    ncontent = mw.ustring.match(ncontent, npattern)
    if type(ncontent) == 'nil' then
        ns = ns .. '[[Категория:th - Ошибка поиска]]' .. (args['def'] or args[4] or '')
    else
        ns = ns .. mw.ustring.gsub(ncontent, '<[^>]*>', '')
    end
    ns = frame:preprocess(ns)
    return ns
end --th

function p.th2 (frame)
    --faster (x5-x7), but uses expensive parser function (title*)
    --transclude header
    --according to template:th --excepts 1st param (t) -> params shifted
    
    local args = frame.args or frame
    local ns, ncontent, npattern = '', ''
    local pname = args['d'] or args['1'] or args[1]
    local pheader = args['h'] or args['2'] or args[2] or 'Описание'
    local pcount = tonumber(args['c'] or args['3'] or args[3]) or 0
    local pdef = args['def'] or args['4'] or args[4] or ''
    local pbylink = (args['bylink'] or args['5'] or args[5] or '1') ~= '0'
    local ntitle
    if not pbylink then
        ncontent = pname
    else
        ntitle = mw.title.new(pname)
        if type(ntitle) ~= 'nil' then
            ncontent = ntitle:getContent()
        else
            ncontent = frame:expandTemplate{ title=':' .. pname }
        end
    end
    npattern = pheader
    if pcount == 0 then
        npattern = '=+' .. npattern .. '=+(.-)=='
    else
        npattern = '=+' .. npattern .. '.-%.-=*(' .. mw.ustring.rep('[%w%s,:;%-%+%<%>]+[%<%>%.%?%!%C]+', pcount) .. ')'
    end
    ncontent = mw.ustring.match(ncontent, npattern)
    if type(ncontent) == 'nil' then
        ns = ns .. '[[Категория:th - Ошибка поиска]]' .. pdef
    else
        ns = ns .. mw.ustring.gsub(ncontent, '<[^>]*>', '')
    end
    if frame.preprocess then
        ns = frame:preprocess(ns)
    end
    --ns=mw.ustring.gsub(ns, '%z', '') --remove zero-char's
    return ns --frame:preprocess(ns)
end --th2

function p.th3 (frame) --tst
    local args = frame.args
    local ns, ncontent, npattern = '', ''
    local ncount = tonumber(args['c'] or args[3]) or 0
    local ntitle
    --ncount = tonumber(ncount)
    --if type(args['t']) ~= 'nil' then ns=args['t'] elseif type(args[1]) ~= 'nil' then ns=args[1] end
    if ((args['bylink'] or args[5] or '1') == '0') then
        ncontent=(args['d'] or args[1])
    else
        ntitle=mw.title.new(args['d'] or args[1])
        if type(ntitle) ~= 'nil' then
            ncontent = ntitle:getContent()
        else
            ncontent = frame:expandTemplate{ title=':' .. (args['d'] or args[1]) }
        end
    end
    npattern = (args['h'] or args[2] or 'Описание')
    if ncount == 0 then
        npattern = '=+' .. npattern .. '=+(.-)=='
    else
        npattern = '=+' .. npattern .. '=+' .. '(' .. mw.ustring.rep('.-[%.%?%!]+%s+', ncount) .. ')[=$]*'
    end
    ncontent = mw.ustring.match(ncontent, npattern)
    if type(ncontent) == 'nil' then
        ns = ns .. '[[Категория:th - Ошибка поиска]]' .. (args['def'] or args[4] or '')
    else
        ns = ns .. mw.ustring.gsub(ncontent, '<[^>]*>', '')
    end
    ns = frame:preprocess(ns)
    --ns=mw.ustring.gsub(ns, '%z', '') --remove zero-char's
    return ns --frame:preprocess(ns)
end --th3


function p.setcat (frame)
    --set category by content
    --1: page; 2,3: text; 4: cat if found; 5: cat if not found
    local args = frame.args
    local t1, t2 = args[2], args[3]
    local nh = ''
    local ptitle = mw.title.new(args[1])
    local pcontent = mw.getCurrentFrame():preprocess('{{:'..ptitle.prefixedText..'}}')
    --nh=mw.ustring.len(pcontent)
    if type(args[3]) == 'nil' then t2 = t1 end
    if mw.ustring.find(pcontent, t2, 1, true) or mw.ustring.find(pcontent, t3, 1, true) then
        nh = args[4]
    else
        nh = args[5]
    end
    --nh=(ptitle.prefixedText or 'nil')
    --nh=#pcontent
    --nh = hasc.hascat(frame)
    --nh = select('#', args)
    
    return nh--..','..args[1]..','..(args[2] or 'nil')..','..(args[3] or 'nil')..','..(args[4] or 'nil')
end --setcat

function p.cats (frame)
    --has category
    local hasc = require('Dev:Pageinfo')
    local args = frame.args
    local nh
    nh = hasc.hascat(frame)
    --nh = select('#', args)
    return args[1]..','..(nh or 'nil')
end --cats

function parseLinks(data, part)
    -- returns {links} w\o [[]]
    -- part: which part of [[link|title]] to return: link, title
    --  part == table: {link: link, title: title}
    if not data then return {} end
    local links = {}
    local patLink = '%[%[([^]]*)%]%]'-- link pattern
    part = part or ''

    function splitLink(link)
        -- раздирает ссылки на части
        local al = mw.text.split(link, '|')
        if part == 'link' then
            link = (#al > 0 and #mw.text.trim(al[1]) > 0) and al[1] or nil
        elseif part == 'title' then
            link = #al > 1 and table.concat(al, '|', 2) or (#mw.text.trim(al[1]) > 0 and al[1] or nil)
        elseif part == 'table' then
            if #al > 0 and #mw.text.trim(al[1]) > 0 then
                link = {
                    link = al[1],
                    title = #al > 1 and table.concat(al, '|', 2) or al[1]
                }
            else
                link = nil
            end
        end
        return link
    end-- splitlink

    function parseLinkHelper(t)
        -- нужна для избежания дублирования кода,
        --  если на выпарсивание ссылок передали массив строк
        -- пользуется переменными родителя
        local ret = {}
        for l in t:gmatch(patLink) do
            l = splitLink(l)
            if l then table.insert(ret, l) end
        end
        if #ret == 0 then
            -- no links (loc w\o [[]]: sanctuary instead of [[sanctuary]])
            t = splitLink(t)
            table.insert(ret, t)
        end
        return ret
    end-- parselinkhelper

    if type(data) == 'table' then
        -- пришёл массив строк
        for _, v in pairs(data) do
            -- все найденные ссылки собираются на одном уровне
            for _, v1 in pairs(parseLinkHelper(v)) do
                table.insert(links, v1)
            end
        end
    else
        links = parseLinkHelper(data)
    end-- if data==table

    return links
end-- parselinks

function tableTrim(tab)
    -- removes zero/empty values from the table
    if #(tab or {}) == 0 then return tab end
    local i = 1
    repeat
        if #(tab[i] or {}) == 0 or #mw.text.trim(tab[i]) == 0 then
            table.remove(tab, i)
        else
            i = i + 1
        end
    until i > #tab
    return tab
end-- tabletrim

function tableSort(el1, el2)
    -- sane table sorting alg ("str" is lesser than "str - str")
    -- strip []
    el1 = mw.ustring.gsub(el1, '[%[%]]', '')
    el2 = mw.ustring.gsub(el2, '[%[%]]', '')

    if (#el1 < #el2) and (mw.ustring.sub(el2, 1, #el1) == el1) then
        -- if el2 includes el1, then el is lesser
        return el1
    elseif (#el2 < #el1) and (mw.ustring.sub(el1, 1, #el2) == el2) then
        -- if el1 includes el2, then el2 is lesser
        return el2
    else
        -- otherwise: just <
        return el1 < el2
    end
end-- tablesort

function p.tlm (frame)
    --template:tlocmission

    local args = frame.args or frame
    local tfull = args['1'] or args[1] or 'В этом разделе указаны задания, связанные с данной локацией. В разделе "Выдаются" указаны задания, которые берут начало в этой локации; а в разделе "Выполняются" находятся задания, для которых данная локация служит промежуточной или конечной.'
    local tin = args['2'] or args[2] or 'В этом разделе указаны задания, которые берут начало в этой локации.'
    local tout = args['3'] or args[3] or 'В этом разделе указаны задания, для которых данная локация служит промежуточной или конечной.'
    local nret = args['4'] or args[4] or tfull -- default
    local ntitle = mw.title.new((args.p and #args.p > 0) and args.p or mw.title.getCurrentTitle().text)
    local mode = args.mode or '' -- auto для автосписка миссий
    local game = mw.text.trim(args.game or '') -- игра
    game = #game > 0 and game or nil

    if mode == 'auto' then
        -- автоматический список миссий
        local nret1 = {}
        local dplText = '{{#dpl:namespace=|uses=template:Задание|linksto=%PAGENAME1%|include={Задание}::Локация взятия:Локация выполнения:Игра|includematch=/%PAGENAME2%/s|includetrim=true|mode=userformat|tablerow=~~%PAGE%,~%%,~%%,~%%|noresultsheader=|noresultsfooter=|suppresserrors=true|allowcachedresults=true}}'
        local tnoresult = 'В этом разделе были бы указаны задания, связанные с данной локацией. Если бы их удалось найти.'
        local there = ' (выполняется здесь же)'
        -- дополнительные миссии (которые невозможно найти автоматически)
        --  формат: миссия1~~миссия2
        local addStart = tableTrim(mw.text.split(args.astart or '', '~~'))
        local addEnd = tableTrim(mw.text.split(args.aend or '', '~~'))
        -- миссии, которые начинаются\заканчиваются здесь
        local mstart = addStart
        local mend = addEnd
        -- defend dpl against stupid chars in the title (<>)
        -- вызов всегда должен быть через mw.ustring, чтобы получить ustring вместо string
        --  баг?: ustring должен быть на каждой строке по дефолту
        local linksto = mw.ustring.gsub(ntitle.text, '[«<>»]', '%')
        local includematch = mw.ustring.gsub(ntitle.text, '([()/])', '\\%1')
        -- .*?: потенциально фейловый паттерн: может гребануть больше, чем надо
        --  на практике пока не встречалось
        includematch = mw.ustring.gsub(includematch, '[«<>»]', '.*?')
        includematch = mw.ustring.gsub(includematch, '[ _]', '[ _]')
        dplText = mw.ustring.gsub(dplText, '%%PAGENAME1%%', linksto)
        dplText = mw.ustring.gsub(dplText, '%%PAGENAME2%%', includematch)
        -- тестовые данные; не предназначены для полной проверки без указания параметра p (плохой формат, -список миссий)
        local dplResult = frame.preprocess and frame:preprocess(dplText) or [=[~~Всемогущий повелитель |~Дожигатель |~[[Дожигатель]] и [[Упавший Гелиос]] |~Borderlands 2~~Друзья на всю жизнь (задание) |~Дожигатель |~[[Развалины Даль]], [[Норы]], [[Упавший Гелиос]] и [[Исследовательский центр "Скарабей"]] |~Borderlands 2~~Защитник (задание) |~Дожигатель |~Развалины Даль |~Borderlands 2~~Клятва Гиппократа |~Дожигатель |~Развалины Даль |~Borderlands 2~~Клятва Гиппократа - Глава 2 |~Дожигатель |~Норы |~Borderlands 2~~Концертная приманка |~Дожигатель |~Гудящая Бездна |~Borderlands 2~~Космический ковбой |~Дожигатель |~Развалины Даль |~Borderlands 2~~Мой хрупкий пони |~Дожигатель |~Упавший Гелиос |~Borderlands 2~~Охотник Вон |~Дожигатель |~Норы |~Borderlands 2~~Поиск образцов |~Дожигатель |~Развалины Даль |~Borderlands 2~~Правитель Вон |~Дожигатель |~Развалины Даль |~Borderlands 2~~Сиренология |~Дожигатель |~Упавший Гелиос |~Borderlands 2~~Точка преткновения |~Дожигатель |~Норы |~Borderlands 2]=]
        nret = tnoresult

        if dplResult and #dplResult > 0 then
            -- do stuff
            -- parse dpl output
            --  ~~title (mission)|~loc1|~loc2|~game
            local dplLines = mw.text.split(dplResult, '~~')
            -- распарсенный список "выполняющихся" миссий
            --  в связи с особенностями заполнения шаблона делать такое же
            --  для "выдающихся" смысла пока что нет
            local addEndParsed = parseLinks(addEnd, 'table')
            -- remove 1st empty row (cuz ~~data -> {, data})
            table.remove(dplLines, 1)
            function findLoc(locs, loc)
                -- find loc in locs
                -- returns loc index or nil
                for i, v in ipairs(locs) do
                    if v.link == loc.link then
                        return i
                    end
                end
                return nil
            end--findloc

            for _, line in ipairs(dplLines) do
                local mis, loc1, loc2, gam = line:match('^[%s%c~]*(.-)[%s%c]*|~(.-)[%s%c]*|~(.-)[%s%c]*|~(.-)[%s%c]*$')
                -- фильтр по игре
                if gam and #gam ~= 0 then
                    gam = mw.text.trim(parseLinks(gam, 'link')[1])-- only 1 game expected is
                    if #gam == 0 then gam = nil end
                else
                    gam = nil
                end

                -- эта миссия уже была добавлена вручную?
                local handmade = findLoc(addEndParsed, {link = mis})
                
                if not handmade and ((not gam or not game) or (gam and game and gam == game)) then
                    --mw.log(loc1, loc2)
                    loc1 = parseLinks(loc1, 'table')
                    loc2 = parseLinks(loc2, 'table')
                    -- локация выполнения
                    for i, v in ipairs(loc2) do
                        local isInLoc1 = findLoc(loc1, v)
                        if v.link == ntitle.text then
                            if isInLoc1 then
                                -- если лока сдачи совпадает с локой взятия,
                                --  то установить here
                                loc1[isInLoc1].here = true
                                table.remove(loc2, i)
                                break
                            end
                            table.insert(mend, '[[' .. mis .. ']]')
                        end
                    end-- for loc2
                    -- если лока сдачи не указана, то = локе взятия
                    -- здесь нельзя определить конкретную локу из списка, потому - первая
                    if #(loc2 or {}) == 0 then loc1[1].here = true end
                    -- локация взятия
                    for i, v in ipairs(loc1) do
                        -- миссия ссылается на текущую страницу?
                        if v.link == ntitle.text then
                            table.insert(mstart, '[[' .. mis .. ']]' .. (v.here and there or ''))
                            break
                        end
                    end-- for loc1
                end-- if gam==game
            end-- for line in dpllines
        end-- if dplresult
        -- process (sort etc) tables here
        table.sort(mstart, tableSort)
        table.sort(mend, tableSort)
        -- strip empty values
        table.insert(mstart, 1, '\n*Выдаются:')
        table.insert(mend, 1, '\n*Выполняются:')
        mstart = #mstart > 1 and table.concat(mstart, '\n**') or nil
        mend = #mend > 1 and table.concat(mend, '\n**') or nil
        -- какую надпись показать?
        if mstart then
            nret = mend and tfull or tin
        else
            nret = mend and tout or tnoresult
        end
        table.insert(nret1, nret)
        --table.insert(nret1, dplText)
        --table.insert(nret1, dplResult)
        table.insert(nret1, mstart)
        table.insert(nret1, mend)
        --return frame.preprocess and frame:preprocess('<pre>' .. table.concat(nret1, '\n----') .. '</pre>') or table.concat(nret1, '\n----')
        return table.concat(nret1)
    else
        -- заполнено вручную
        local ncontent = ntitle:getContent()
        local npattern = '==Задания==.-'
        local findresult1, findresult2 = nil, nil
        findresult1 = mw.ustring.find(ncontent, npattern..'\n%*Выдаются', 1, false)
        findresult2 = mw.ustring.find(ncontent, npattern..'\n%*Выполняются', 1, false)
        if (findresult1 and findresult2) then
            nret = tfull
        elseif findresult1 then
            nret = tin
        elseif findresult2 then
            nret = tout
        end
        return nret
    end-- if mode==auto
end-- tlm

function p.getParent (f)
    --returns top parent frame
    if f.getParent and f:getParent() then return p.getParent(f:getParent()) else return f end
end

function p.printarg (frame)
    --returns arg=value; order is not guaranteed
    local f = p.getParent(frame)
    local args = f.args
    local exclude = mw.text.split(frame.args.exclude or '', ',')
    local s = ''
    if exclude and #exclude == 1 and #exclude[1] == 0 then exclude = {} end
    function excluded (name)
        --returns true if param should be excluded
        if #exclude == 0 then return false end
        for i, v in pairs(exclude) do
            if v == name then return true end
        end
        return false
    end--excluded
    for i, v in pairs(args) do
        if not excluded(i) then
            --do not add = to unnamed and 1=
            s = s .. (tonumber(i) and '' or (i .. '=')) .. v
        end
    end
    return s
end--printarg

function p.dexname (frame)
    --return ext name
    local src = frame.args[1] or frame.args['src'] or ''
    local ext = nil
    if src == '' then
        return
    end
    if mw.ustring.find(src, '\'\"\`UNIQ.-\-(.-)\-.-QINU\`\"\'', 1, false) then
        ext = mw.ustring.gsub(mw.ustring.gsub(src, '\'\"\`UNIQ.-\-(.-)\-.-QINU\`\"\'', '%1'),'^([%s%c]*)(.-)([%c%s]*)$','%2')
    end
    return ext
end --dexname

function p.iif (cond, t, f)
    if cond then
        return t
    else
        return f
    end
end

function p.bit (b)
  return 2 ^ (b - 1)  -- 1-based indexing
end

-- Typical call:  if hasbit(x, bit(3)) then ...
function p.hasbit (x, b)
  return x % (b + b) >= b
end

function p.setbit (x, b)
  return hasbit(x, b) and x or x + b
end

function p.clearbit (x, b)
  return hasbit(x, b) and x - b or x
end

function p_trim( s )
    if s and not(s:match('^%s*$')) then
        return mw.text.trim( s ); --  If not nil nor empty.
    end
end

-- экспорт фигни для отладки
p.parseLinks = parseLinks
p.tableTrim = tableTrim
p.tableSort = tableSort

return p
--</syntaxhighlight>