Module:Data to Table: Difference between revisions
ReisuDesign (talk | contribs) Created page with "local p = {} local CleanTT = require('Module:tt').CleanTT local lib = require('Module:Feature') local ne = lib.isNotEmpty local block = false --check for if the entry is the first on the level local first = {true, true, true} --check for if the entry is right after the end of a deeper level local previous = {false, false, false} --escape characters local function clean(txt) txt = txt:gsub('&(%a+;)','&%1'):gsub('\'','\\''):gsub('<', '<'):gsub('\'', '\\\'') r..." |
ReisuDesign (talk | contribs) No edit summary |
||
| (10 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
local p = {} | local p = {} | ||
local CleanTT = require('Module:tt').CleanTT | local CleanTT = require('Module:tt').CleanTT | ||
local lib = require('Module:Feature') | local lib = require('Module:Feature') | ||
local ne = lib.isNotEmpty | local ne = lib.isNotEmpty | ||
local block = false | local block = false | ||
-- | |||
-- Check if the entry is the first on the level | |||
local first = {true, true, true} | local first = {true, true, true} | ||
-- | |||
-- Check if the entry is right after the end of a deeper level | |||
local previous = {false, false, false} | local previous = {false, false, false} | ||
-- | |||
-- Escape characters for Lua strings | |||
local function clean(txt) | local function clean(txt) | ||
return txt | return txt | ||
:gsub('\\', '\\\\') | |||
:gsub('\'', '\\\'') | |||
end | end | ||
-- Parse DPL data | |||
local function parseInfo(info, frame) | |||
if not info then | |||
return {} | |||
end | |||
if ne(frame.args.cleanTT) then | |||
info = CleanTT(info) | |||
end | |||
return lib.parseTemplateFormat( | |||
info, | |||
';;', | |||
'$$$', | |||
'==' | |||
) | |||
end | |||
-- Get grouped JSON data | |||
local function getJsonGroups(frame) | |||
local groups = {} | |||
local i = 1 | |||
while true do | |||
local groupName = frame.args['group' .. i] | |||
local groupInfo = frame.args['group' .. i .. '_info'] | |||
-- Stop when neither argument exists. | |||
if groupName == nil and groupInfo == nil then | |||
break | |||
end | |||
if ne(groupName) and groupInfo ~= nil then | |||
local info = parseInfo(groupInfo, frame) | |||
local entries = {} | |||
-- Collect entries | |||
for _, entry in lib.spairs( | |||
info, | |||
function(tVal, a, b) | |||
local aPage = tostring(tVal[a].page or a) | |||
local bPage = tostring(tVal[b].page or b) | |||
return aPage < bPage | |||
end | |||
) do | |||
if ne(entry.page) then | |||
entries[#entries + 1] = entry.page | |||
end | |||
end | |||
-- Only add the group if it actually contains entries. | |||
if #entries > 0 then | |||
groups[groupName] = entries | |||
end | |||
end | |||
i = i + 1 | |||
end | |||
return groups | |||
end | |||
-- Format grouped data as readable JSON | |||
function p.FormatJSONGroups(groups) | |||
local result = {} | |||
table.insert(result, '{') | |||
-- Get group names | |||
local groupNames = {} | |||
for name in pairs(groups) do | |||
groupNames[#groupNames + 1] = name | |||
end | |||
table.sort(groupNames) | |||
-- Build each group | |||
for i, name in ipairs(groupNames) do | |||
local entries = groups[name] | |||
local groupComma = (i < #groupNames) and ',' or '' | |||
table.insert( | |||
result, | |||
'\t' .. | |||
mw.text.jsonEncode(name) .. | |||
': [' | |||
) | |||
for j, entry in ipairs(entries) do | |||
local entryComma = (j < #entries) and ',' or '' | |||
table.insert( | |||
result, | |||
'\t\t' .. | |||
mw.text.jsonEncode(entry) .. | |||
entryComma | |||
) | |||
end | |||
table.insert( | |||
result, | |||
'\t]' .. groupComma | |||
) | |||
end | |||
table.insert(result, '}') | |||
return table.concat(result, '\n') | |||
end | |||
function p.main(frame) | function p.main(frame) | ||
local info = {} | local info = {} | ||
local dataKeys = {} | local dataKeys = {} | ||
local rawKeys = {} | |||
-- ============================================================ | |||
-- JSON MODE | |||
-- ============================================================ | |||
if tostring(frame.args.json) == '1' then | |||
-- -------------------------------------------------------- | |||
-- Grouped JSON mode | |||
-- -------------------------------------------------------- | |||
local groups = getJsonGroups(frame) | |||
-- If groups were supplied, output the complete JSON object. | |||
if next(groups) ~= nil then | |||
local jsonText = p.FormatJSONGroups(groups) | |||
return frame:extensionTag( | |||
'syntaxhighlight', | |||
jsonText, | |||
{ | |||
lang = 'json', | |||
copy = '' | |||
} | |||
) | |||
end | |||
-- -------------------------------------------------------- | |||
-- Simple JSON array mode | |||
-- -------------------------------------------------------- | |||
if frame.args.info then | |||
info = parseInfo(frame.args.info, frame) | |||
local jsonEntries = {} | |||
for _, entry in lib.spairs( | |||
info, | |||
function(tVal, a, b) | |||
local aPage = tostring(tVal[a].page or a) | |||
local bPage = tostring(tVal[b].page or b) | |||
return aPage < bPage | |||
end | |||
) do | |||
if ne(entry.page) then | |||
jsonEntries[#jsonEntries + 1] = entry.page | |||
end | |||
end | |||
local jsonText = mw.text.jsonEncode(jsonEntries) | |||
return frame:extensionTag( | |||
'syntaxhighlight', | |||
jsonText, | |||
{ | |||
lang = 'json', | |||
copy = '' | |||
} | |||
) | |||
end | |||
return '' | |||
end | |||
-- ============================================================ | |||
-- NORMAL LUA MODE | |||
-- ============================================================ | |||
-- Parse DPL data | |||
if frame.args.info then | if frame.args.info then | ||
info = parseInfo(frame.args.info, frame) | |||
if frame.args. | else | ||
if ne(frame.args.block) then block = true end | return '' | ||
end | |||
-- Parse keys | |||
if frame.args.keys then | |||
dataKeys = p.ParseKeys(frame.args.keys) | |||
end | |||
-- Parse unquoted/raw keys | |||
if frame.args.unquoted then | |||
rawKeys = p.ParseKeys(frame.args.unquoted) | |||
end | |||
-- Block formatting | |||
if ne(frame.args.block) then | |||
block = true | |||
end | |||
local sortkey = frame.args.sortkey or 'page' | local sortkey = frame.args.sortkey or 'page' | ||
local subtable = frame.args.subtable | local subtable = frame.args.subtable | ||
local | |||
-- Constant value mode | |||
for _,entry in lib.spairs(info, function(tVal, a, b) if sortkey == 'INPUT' then return a < b else return (tVal[a][sortkey] or '') < (tVal[b][sortkey] or '') end end) do | -- If |value=day is supplied, every page gets that value. | ||
local constantValue = frame.args.value | |||
local valueUnquoted = false | |||
if constantValue ~= nil then | |||
for _, rawKey in ipairs(rawKeys) do | |||
if rawKey == 'value' then | |||
valueUnquoted = true | |||
break | |||
end | |||
end | |||
end | |||
-- Check whether a key should be output as raw Lua | |||
local function isUnquotedKey(key) | |||
for _, rawKey in ipairs(rawKeys) do | |||
if rawKey == key then | |||
return true | |||
end | |||
end | |||
return false | |||
end | |||
-- Build result | |||
local result = mw.html.create() | |||
:wikitext('return {\n') | |||
for _, entry in lib.spairs( | |||
info, | |||
function(tVal, a, b) | |||
if sortkey == 'INPUT' then | |||
return a < b | |||
else | |||
return (tVal[a][sortkey] or '') < | |||
(tVal[b][sortkey] or '') | |||
end | |||
end | |||
) do | |||
if ne(entry.page) then | if ne(entry.page) then | ||
entry.page = clean(entry.page) | entry.page = clean(entry.page) | ||
if #dataKeys == 0 then | |||
result:wikitext('\t\'', entry.page, '\',\n') | |||
-- ---------------------------------------------------- | |||
-- Constant value mode | |||
-- ---------------------------------------------------- | |||
if constantValue ~= nil and #dataKeys == 0 then | |||
local value | |||
if valueUnquoted then | |||
value = clean(constantValue) | |||
else | |||
value = '\'' .. | |||
clean(constantValue) .. | |||
'\'' | |||
end | |||
result:wikitext( | |||
'\t[\'', | |||
entry.page, | |||
'\'] = ', | |||
value, | |||
',\n' | |||
) | |||
-- ---------------------------------------------------- | |||
-- No data keys | |||
-- ---------------------------------------------------- | |||
elseif #dataKeys == 0 then | |||
result:wikitext( | |||
'\t\'', | |||
entry.page, | |||
'\',\n' | |||
) | |||
-- ---------------------------------------------------- | |||
-- Normal nested table mode | |||
-- ---------------------------------------------------- | |||
else | else | ||
-- Reset formatting state for every entry | |||
first = {true, true, true} | first = {true, true, true} | ||
previous = {false, false, false} | previous = {false, false, false} | ||
local valid = false | local valid = false | ||
local values = mw.html.create() | local values = mw.html.create() | ||
values:wikitext('\t[\'', entry.page, '\']') | |||
-- | -- Entry name | ||
values:wikitext( | |||
'\t[\'', | |||
entry.page, | |||
'\'] = {' | |||
) | |||
-- Build entry | |||
local function parseLevels(K, level) | local function parseLevels(K, level) | ||
level = level or 1 | level = level or 1 | ||
if type(K) == 'string' | |||
if type(K) == 'string' then | |||
valid = true | valid = true | ||
values:node(p.BuildEntry(entry[K], K, level, subtable)) | |||
-- Use the value if present. | |||
-- BuildEntry handles missing values. | |||
values:node( | |||
p.BuildEntry( | |||
entry[K], | |||
K, | |||
level, | |||
subtable, | |||
isUnquotedKey(K) | |||
) | |||
) | |||
elseif type(K) == 'table' then | elseif type(K) == 'table' then | ||
level = level + 1 | level = level + 1 | ||
for _, K2 in ipairs(K) do | for _, K2 in ipairs(K) do | ||
parseLevels(K2, level) | parseLevels(K2, level) | ||
end | end | ||
values:wikitext( | |||
values:wikitext( | |||
'\n', | |||
string.rep('\t', level - 1), | |||
'},' | |||
) | |||
end | end | ||
end | end | ||
parseLevels(dataKeys, 1) | parseLevels(dataKeys, 1) | ||
-- | |||
-- Return table if there is valid data | |||
if valid then | if valid then | ||
values:wikitext('\n') | |||
result:node(values) | result:node(values) | ||
else | else | ||
result:wikitext('\t\'', entry.page, '\',\n') | |||
result:wikitext( | |||
'\t\'', | |||
entry.page, | |||
'\',\n' | |||
) | |||
end | end | ||
end | end | ||
end | |||
end | end | ||
result:wikitext('}') | result:wikitext('}') | ||
-- ============================================================ | |||
-- LUA SYNTAX HIGHLIGHT | |||
-- ============================================================ | |||
local luaText = tostring(result) | |||
-- Decode HTML entities introduced by mw.html | |||
luaText = mw.text.decode(luaText) | |||
local output = frame:extensionTag( | |||
'syntaxhighlight', | |||
luaText, | |||
{ | |||
lang = 'lua', | |||
copy = '' | |||
} | |||
) | |||
-- ============================================================ | |||
-- COPY BUTTON | |||
-- ============================================================ | |||
-- Keep Module:CopyText for normal Lua output. | |||
if frame.args.copy then | if frame.args.copy then | ||
output = require('Module:CopyText')._main({ | |||
output, | |||
frame.args.copy, | |||
'Copy Table' | |||
}) | |||
end | end | ||
return | |||
return output | |||
end | end | ||
-- | |||
function p.BuildEntry(value, key, level, subtable) | -- ================================================================ | ||
-- FORMAT DATA | |||
-- ================================================================ | |||
function p.BuildEntry(value, key, level, subtable, unquoted) | |||
level = level or 1 | level = level or 1 | ||
local container = mw.html.create() | local container = mw.html.create() | ||
if | |||
if | |||
value = | -- Default missing values | ||
if value == nil or value == '' then | |||
if key == 'number' then | |||
value = '0' | |||
unquoted = true | |||
else | else | ||
value = | |||
value = '' | |||
unquoted = false | |||
end | end | ||
end | |||
-- Format value | |||
if subtable and value ~= '' and value:find(subtable) then | |||
value = lib.split( | |||
clean(value), | |||
subtable | |||
) | |||
value = '{ \'' .. | |||
table.concat(value, '\', \'') .. | |||
' }' | |||
elseif unquoted and value ~= '' then | |||
-- Raw Lua value | |||
value = clean(value) | |||
else | |||
-- String value | |||
value = '\'' .. | |||
clean(value) .. | |||
'\'' | |||
end | |||
-- Format key as ['key'] | |||
local formattedKey = | |||
'[\'' .. clean(key) .. '\']' | |||
-- First item on this level | |||
if first[level] then | |||
container:wikitext( | |||
'\n', | |||
string.rep('\t', level), | |||
formattedKey, | |||
' = ', | |||
value, | |||
',' | |||
) | |||
first[level] = false | |||
if level > 2 then | |||
first[2] = false | |||
end | end | ||
if level > 1 then | |||
first[1] = false | |||
end | |||
-- Subsequent item on this level | |||
else | |||
container:wikitext( | |||
'\n', | |||
string.rep('\t', level), | |||
formattedKey, | |||
' = ', | |||
value, | |||
',' | |||
) | |||
previous[level] = false | |||
end | end | ||
return container | return container | ||
end | end | ||
-- | |||
-- ================================================================ | |||
-- PARSE DATA KEYS INTO SPECIFIED LEVELS | |||
-- ================================================================ | |||
function p.ParseKeys(keys) | function p.ParseKeys(keys) | ||
keys = lib.split(keys, ';;') | keys = lib.split(keys, ';;') | ||
for i, key in pairs(keys) do | for i, key in pairs(keys) do | ||
if key:find('//') then | if key:find('//') then | ||
local keys2 = lib.split(key, '//') | local keys2 = lib.split(key, '//') | ||
for i2, key2 in pairs(keys2) do | for i2, key2 in pairs(keys2) do | ||
if key2:find('@@') then | if key2:find('@@') then | ||
keys2[i2] = lib.split(key, '@@') | keys2[i2] = lib.split(key, '@@') | ||
end | end | ||
end | end | ||
keys[i] = keys2 | keys[i] = keys2 | ||
end | end | ||
end | end | ||
return keys | return keys | ||
end | end | ||
return p | return p | ||